diff options
author | Wes Haggard <weshaggard@users.noreply.github.com> | 2017-08-05 01:42:50 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-05 01:42:50 +0300 |
commit | bdb916433d3415302ca3931db3b19b7f79b04e44 (patch) | |
tree | 8a4ccd7285d9d7855e0546a458336d0ff0b5cac5 /src | |
parent | 72efc42a1f0cb9fe8a364558bba2126fe2fbf515 (diff) | |
parent | db069e0f66c92fafc704dcf6f833817818b3b465 (diff) |
Merge pull request #22920 from weshaggard/ServiceBase
Add ServiceBase
Diffstat (limited to 'src')
39 files changed, 2352 insertions, 875 deletions
diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.ChangeServiceConfig2.cs b/src/Common/src/Interop/Windows/advapi32/Interop.ChangeServiceConfig2.cs new file mode 100644 index 0000000000..9b92147954 --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.ChangeServiceConfig2.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool ChangeServiceConfig2(IntPtr serviceHandle, uint infoLevel, ref SERVICE_DESCRIPTION serviceDesc); + + [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool ChangeServiceConfig2(IntPtr serviceHandle, uint infoLevel, ref SERVICE_DELAYED_AUTOSTART_INFO serviceDesc); + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.CreateService.cs b/src/Common/src/Interop/Windows/advapi32/Interop.CreateService.cs new file mode 100644 index 0000000000..9bb9a73678 --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.CreateService.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, SetLastError = true)] + public extern static IntPtr CreateService(IntPtr databaseHandle, string serviceName, string displayName, int access, int serviceType, + int startType, int errorControl, string binaryPath, string loadOrderGroup, IntPtr pTagId, string dependencies, + string servicesStartName, string password); + + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.DeleteService.cs b/src/Common/src/Interop/Windows/advapi32/Interop.DeleteService.cs new file mode 100644 index 0000000000..4bd1b9801b --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.DeleteService.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, SetLastError = true)] + public extern static bool DeleteService(IntPtr serviceHandle); + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandler.cs b/src/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandler.cs new file mode 100644 index 0000000000..cbf6a99eb9 --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandler.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, SetLastError = true)] + public extern static IntPtr RegisterServiceCtrlHandler(string serviceName, Delegate callback); + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandlerEx.cs b/src/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandlerEx.cs new file mode 100644 index 0000000000..f2b494abf3 --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandlerEx.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, SetLastError = true)] + public extern static IntPtr RegisterServiceCtrlHandlerEx(string serviceName, Delegate callback, IntPtr userData); + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DELAYED_AUTOSTART_INFO.cs b/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DELAYED_AUTOSTART_INFO.cs new file mode 100644 index 0000000000..249230d7dd --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DELAYED_AUTOSTART_INFO.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct SERVICE_DELAYED_AUTOSTART_INFO + { + public bool fDelayedAutostart; + } + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DESCRIPTION.cs b/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DESCRIPTION.cs new file mode 100644 index 0000000000..53f1bfd4b8 --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DESCRIPTION.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct SERVICE_DESCRIPTION + { + public IntPtr description; + } + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_TABLE_ENTRY.cs b/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_TABLE_ENTRY.cs new file mode 100644 index 0000000000..59ab422f87 --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.SERVICE_TABLE_ENTRY.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [StructLayout(LayoutKind.Sequential)] + public class SERVICE_TABLE_ENTRY + { + public IntPtr name; + public Delegate callback; + } + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.ServiceControlDelegates.cs b/src/Common/src/Interop/Windows/advapi32/Interop.ServiceControlDelegates.cs new file mode 100644 index 0000000000..8c4034b9b4 --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.ServiceControlDelegates.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + public delegate void ServiceMainCallback(int argCount, IntPtr argPointer); + public delegate void ServiceControlCallback(int control); + public delegate int ServiceControlCallbackEx(int control, int eventType, IntPtr eventData, IntPtr eventContext); + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.ServiceProcessOptions.cs b/src/Common/src/Interop/Windows/advapi32/Interop.ServiceProcessOptions.cs index 20eaa5e77f..5e4a93b38f 100644 --- a/src/Common/src/Interop/Windows/advapi32/Interop.ServiceProcessOptions.cs +++ b/src/Common/src/Interop/Windows/advapi32/Interop.ServiceProcessOptions.cs @@ -2,13 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Runtime.InteropServices; + internal partial class Interop { internal partial class Advapi32 { internal partial class AcceptOptions { + internal const int ACCEPT_POWEREVENT = 0x00000040; internal const int ACCEPT_PAUSE_CONTINUE = 0x00000002; + internal const int ACCEPT_SESSIONCHANGE = 0x00000080; internal const int ACCEPT_SHUTDOWN = 0x00000004; internal const int ACCEPT_STOP = 0x00000001; } @@ -16,10 +21,21 @@ internal partial class Interop internal partial class ControlOptions { internal const int CONTROL_CONTINUE = 0x00000003; + internal const int CONTROL_INTERROGATE = 0x00000004; internal const int CONTROL_PAUSE = 0x00000002; + internal const int CONTROL_POWEREVENT = 0x0000000D; + internal const int CONTROL_SESSIONCHANGE = 0x0000000E; + internal const int CONTROL_SHUTDOWN = 0x00000005; internal const int CONTROL_STOP = 0x00000001; } + internal partial class ServiceConfigOptions + { + internal const int SERVICE_CONFIG_DESCRIPTION = 0x00000001; + internal const int SERVICE_CONFIG_FAILURE_ACTIONS = 0x00000002; + internal const int SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 0x00000003; + } + internal partial class ServiceOptions { internal const int SERVICE_QUERY_CONFIG = 0x0001; @@ -44,6 +60,7 @@ internal partial class Interop SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL; + internal const int STANDARD_RIGHTS_DELETE = 0x00010000; internal const int STANDARD_RIGHTS_REQUIRED = 0x000F0000; } @@ -70,6 +87,30 @@ internal partial class Interop SERVICE_TYPE_INTERACTIVE_PROCESS; } + internal partial class ServiceAccessOptions + { + internal const int ACCESS_TYPE_CHANGE_CONFIG = 0x0002; + internal const int ACCESS_TYPE_ENUMERATE_DEPENDENTS = 0x0008; + internal const int ACCESS_TYPE_INTERROGATE = 0x0080; + internal const int ACCESS_TYPE_PAUSE_CONTINUE = 0x0040; + internal const int ACCESS_TYPE_QUERY_CONFIG = 0x0001; + internal const int ACCESS_TYPE_QUERY_STATUS = 0x0004; + internal const int ACCESS_TYPE_START = 0x0010; + internal const int ACCESS_TYPE_STOP = 0x0020; + internal const int ACCESS_TYPE_USER_DEFINED_CONTROL = 0x0100; + internal const int ACCESS_TYPE_ALL = + ServiceOptions.STANDARD_RIGHTS_REQUIRED | + ACCESS_TYPE_QUERY_CONFIG | + ACCESS_TYPE_CHANGE_CONFIG | + ACCESS_TYPE_QUERY_STATUS | + ACCESS_TYPE_ENUMERATE_DEPENDENTS | + ACCESS_TYPE_START | + ACCESS_TYPE_STOP | + ACCESS_TYPE_PAUSE_CONTINUE | + ACCESS_TYPE_INTERROGATE | + ACCESS_TYPE_USER_DEFINED_CONTROL; + } + internal partial class ServiceStartModes { internal const int START_TYPE_BOOT = 0x00000000; @@ -104,11 +145,57 @@ internal partial class Interop internal const int STATE_STOP_PENDING = 0x00000003; } + internal partial class ServiceStartErrorModes + { + internal const int ERROR_CONTROL_CRITICAL = 0x00000003; + internal const int ERROR_CONTROL_IGNORE = 0x00000000; + internal const int ERROR_CONTROL_NORMAL = 0x00000001; + internal const int ERROR_CONTROL_SEVERE = 0x00000002; + } + internal partial class ServiceControllerOptions { + internal const int SC_ENUM_PROCESS_INFO = 0; internal const int SC_MANAGER_CONNECT = 0x0001; + internal const int SC_MANAGER_CREATE_SERVICE = 0x0002; internal const int SC_MANAGER_ENUMERATE_SERVICE = 0x0004; - internal const int SC_ENUM_PROCESS_INFO = 0; + internal const int SC_MANAGER_LOCK = 0x0008; + internal const int SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020; + internal const int SC_MANAGER_QUERY_LOCK_STATUS = 0x0010; + internal const int SC_MANAGER_ALL = + ServiceOptions.STANDARD_RIGHTS_REQUIRED | + SC_MANAGER_CONNECT | + SC_MANAGER_CREATE_SERVICE | + SC_MANAGER_ENUMERATE_SERVICE | + SC_MANAGER_LOCK | + SC_MANAGER_QUERY_LOCK_STATUS | + SC_MANAGER_MODIFY_BOOT_CONFIG; + } + + internal partial class PowerBroadcastStatus + { + internal const int PBT_APMBATTERYLOW = 0x0009; + internal const int PBT_APMOEMEVENT = 0x000B; + internal const int PBT_APMPOWERSTATUSCHANGE = 0x000A; + internal const int PBT_APMQUERYSUSPEND = 0x0000; + internal const int PBT_APMQUERYSUSPENDFAILED = 0x0002; + internal const int PBT_APMRESUMEAUTOMATIC = 0x0012; + internal const int PBT_APMRESUMECRITICAL = 0x0006; + internal const int PBT_APMRESUMESUSPEND = 0x0007; + internal const int PBT_APMSUSPEND = 0x0004; + } + + internal partial class SessionStateChange + { + internal const int WTS_CONSOLE_CONNECT = 0x1; + internal const int WTS_CONSOLE_DISCONNECT = 0x2; + internal const int WTS_REMOTE_CONNECT = 0x3; + internal const int WTS_REMOTE_DISCONNECT = 0x4; + internal const int WTS_SESSION_LOGON = 0x5; + internal const int WTS_SESSION_LOGOFF = 0x6; + internal const int WTS_SESSION_LOCK = 0x7; + internal const int WTS_SESSION_UNLOCK = 0x8; + internal const int WTS_SESSION_REMOTE_CONTROL = 0x9; } } } diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.SetServiceStatus.cs b/src/Common/src/Interop/Windows/advapi32/Interop.SetServiceStatus.cs new file mode 100644 index 0000000000..e20f22006f --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.SetServiceStatus.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, SetLastError = true)] + public unsafe extern static bool SetServiceStatus(IntPtr serviceStatusHandle, SERVICE_STATUS* status); + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.StartServiceCtrlDispatcher.cs b/src/Common/src/Interop/Windows/advapi32/Interop.StartServiceCtrlDispatcher.cs new file mode 100644 index 0000000000..a6773ae5ef --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.StartServiceCtrlDispatcher.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, SetLastError = true)] + public extern static bool StartServiceCtrlDispatcher(IntPtr entry); + } +} diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.WTSSESSION_NOTIFICATION.cs b/src/Common/src/Interop/Windows/advapi32/Interop.WTSSESSION_NOTIFICATION.cs new file mode 100644 index 0000000000..86503af47a --- /dev/null +++ b/src/Common/src/Interop/Windows/advapi32/Interop.WTSSESSION_NOTIFICATION.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class WTSSESSION_NOTIFICATION + { + public int size; + public int sessionId; + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/System.ServiceProcess.ServiceController.sln b/src/System.ServiceProcess.ServiceController/System.ServiceProcess.ServiceController.sln index ac2c5a484e..a4b8be352c 100644 --- a/src/System.ServiceProcess.ServiceController/System.ServiceProcess.ServiceController.sln +++ b/src/System.ServiceProcess.ServiceController/System.ServiceProcess.ServiceController.sln @@ -2,7 +2,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceProcess.ServiceController.Tests", "tests\System.ServiceProcess.ServiceController.Tests\System.ServiceProcess.ServiceController.Tests.csproj", "{F7D9984B-02EB-4573-84EF-00FFFBFB872C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceProcess.ServiceController.TestService", "tests\System.ServiceProcess.ServiceController.TestService\System.ServiceProcess.ServiceController.TestService.csproj", "{E62B874D-1A0D-41BC-8CFC-9E09D0860A77}" + ProjectSection(ProjectDependencies) = postProject + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05} = {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceProcess.ServiceController.Tests", "tests\System.ServiceProcess.ServiceController.Tests.csproj", "{F7D9984B-02EB-4573-84EF-00FFFBFB872C}" ProjectSection(ProjectDependencies) = postProject {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05} = {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05} EndProjectSection @@ -26,10 +31,14 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Any CPU.ActiveCfg = netstandard-Windows_NT-Debug|Any CPU - {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Any CPU.Build.0 = netstandard-Windows_NT-Debug|Any CPU - {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Any CPU.ActiveCfg = netstandard-Windows_NT-Release|Any CPU - {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Any CPU.Build.0 = netstandard-Windows_NT-Release|Any CPU + {E62B874D-1A0D-41BC-8CFC-9E09D0860A77}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {E62B874D-1A0D-41BC-8CFC-9E09D0860A77}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {E62B874D-1A0D-41BC-8CFC-9E09D0860A77}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {E62B874D-1A0D-41BC-8CFC-9E09D0860A77}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Debug|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Any CPU.Build.0 = netcoreapp-Windows_NT-Debug|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Release|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Any CPU.Build.0 = netcoreapp-Windows_NT-Release|Any CPU {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Debug|Any CPU.ActiveCfg = netstandard-Windows_NT-Debug|Any CPU {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Debug|Any CPU.Build.0 = netstandard-Windows_NT-Debug|Any CPU {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Release|Any CPU.ActiveCfg = netstandard-Windows_NT-Release|Any CPU @@ -43,6 +52,7 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {E62B874D-1A0D-41BC-8CFC-9E09D0860A77} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} {F7D9984B-02EB-4573-84EF-00FFFBFB872C} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD} {8479566D-6FA5-4241-9D66-524BEC4C19BD} = {2E666815-2EDB-464B-9DF6-380BF4789AD4} diff --git a/src/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.cs b/src/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.cs index 5bc0811eac..e10f43db51 100644 --- a/src/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.cs +++ b/src/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.cs @@ -5,28 +5,75 @@ // Changes to this file must follow the http://aka.ms/api-review process. // ------------------------------------------------------------------------------ - namespace System.ServiceProcess { - public partial class ServiceController : System.IDisposable + public enum PowerBroadcastStatus + { + BatteryLow = 9, + OemEvent = 11, + PowerStatusChange = 10, + QuerySuspend = 0, + QuerySuspendFailed = 2, + ResumeAutomatic = 18, + ResumeCritical = 6, + ResumeSuspend = 7, + Suspend = 4, + } + public partial class ServiceBase : System.ComponentModel.Component + { + public const int MaxNameLength = 80; + public ServiceBase() { } + [System.ComponentModel.DefaultValueAttribute(false)] + public bool CanHandlePowerEvent { get { throw null; } set { } } + [System.ComponentModel.DefaultValueAttribute(false)] + public bool CanHandleSessionChangeEvent { get { throw null; } set { } } + [System.ComponentModel.DefaultValueAttribute(false)] + public bool CanPauseAndContinue { get { throw null; } set { } } + [System.ComponentModel.DefaultValueAttribute(false)] + public bool CanShutdown { get { throw null; } set { } } + [System.ComponentModel.DefaultValueAttribute(true)] + public bool CanStop { get { throw null; } set { } } + public int ExitCode { get { throw null; } set { } } + [System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)(2))] + protected System.IntPtr ServiceHandle { get { throw null; } } + public string ServiceName { get { throw null; } set { } } + protected override void Dispose(bool disposing) { } + protected virtual void OnContinue() { } + protected virtual void OnCustomCommand(int command) { } + protected virtual void OnPause() { } + protected virtual bool OnPowerEvent(System.ServiceProcess.PowerBroadcastStatus powerStatus) { throw null; } + protected virtual void OnSessionChange(System.ServiceProcess.SessionChangeDescription changeDescription) { } + protected virtual void OnShutdown() { } + protected virtual void OnStart(string[] args) { } + protected virtual void OnStop() { } + public void RequestAdditionalTime(int milliseconds) { } + public static void Run(System.ServiceProcess.ServiceBase service) { } + public static void Run(System.ServiceProcess.ServiceBase[] services) { } + [System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)(1))] + public void ServiceMainCallback(int argCount, System.IntPtr argPointer) { } + public void Stop() { } + } + public partial class ServiceController : System.ComponentModel.Component { + public ServiceController() { } public ServiceController(string name) { } public ServiceController(string name, string machineName) { } public bool CanPauseAndContinue { get { throw null; } } public bool CanShutdown { get { throw null; } } public bool CanStop { get { throw null; } } public System.ServiceProcess.ServiceController[] DependentServices { get { throw null; } } - public string DisplayName { get { throw null; } } - public string MachineName { get { throw null; } } + public string DisplayName { get { throw null; } set { } } + public string MachineName { get { throw null; } set { } } public System.Runtime.InteropServices.SafeHandle ServiceHandle { get { throw null; } } - public string ServiceName { get { throw null; } } + public string ServiceName { get { throw null; } set { } } public System.ServiceProcess.ServiceController[] ServicesDependedOn { get { throw null; } } public System.ServiceProcess.ServiceType ServiceType { get { throw null; } } public System.ServiceProcess.ServiceStartMode StartType { get { throw null; } } public System.ServiceProcess.ServiceControllerStatus Status { get { throw null; } } + public void Close() { } public void Continue() { } - public void Dispose() { } - protected virtual void Dispose(bool disposing) { } + protected override void Dispose(bool disposing) { } + public void ExecuteCommand(int command) { } public static System.ServiceProcess.ServiceController[] GetDevices() { throw null; } public static System.ServiceProcess.ServiceController[] GetDevices(string machineName) { throw null; } public static System.ServiceProcess.ServiceController[] GetServices() { throw null; } @@ -68,11 +115,34 @@ namespace System.ServiceProcess Win32OwnProcess = 16, Win32ShareProcess = 32, } - public partial class TimeoutException : System.Exception + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct SessionChangeDescription + { + public System.ServiceProcess.SessionChangeReason Reason { get { throw null; } } + public int SessionId { get { throw null; } } + public override bool Equals(object obj) { throw null; } + public bool Equals(System.ServiceProcess.SessionChangeDescription changeDescription) { throw null; } + public override int GetHashCode() { throw null; } + public static bool operator ==(System.ServiceProcess.SessionChangeDescription a, System.ServiceProcess.SessionChangeDescription b) { throw null; } + public static bool operator !=(System.ServiceProcess.SessionChangeDescription a, System.ServiceProcess.SessionChangeDescription b) { throw null; } + } + public enum SessionChangeReason + { + ConsoleConnect = 1, + ConsoleDisconnect = 2, + RemoteConnect = 3, + RemoteDisconnect = 4, + SessionLock = 7, + SessionLogoff = 6, + SessionLogon = 5, + SessionRemoteControl = 9, + SessionUnlock = 8, + } + public partial class TimeoutException : System.SystemException { public TimeoutException() { } + protected TimeoutException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public TimeoutException(string message) { } public TimeoutException(string message, System.Exception innerException) { } - protected TimeoutException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } } diff --git a/src/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj b/src/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj index 5d6d29f3cd..dc2cae1910 100644 --- a/src/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj +++ b/src/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj @@ -14,7 +14,21 @@ </ItemGroup> <ItemGroup Condition="'$(TargetGroup)' == 'netfx'"> <Reference Include="mscorlib" /> + <Reference Include="System" /> <Reference Include="System.ServiceProcess" /> </ItemGroup> + <ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'"> + <Reference Include="Microsoft.Win32.Primitives" /> + <Reference Include="System.Collections" /> + <Reference Include="System.Console" /> + <Reference Include="System.ComponentModel.Primitives" /> + <Reference Include="System.Diagnostics.Debug" /> + <Reference Include="System.Diagnostics.Tools" /> + <Reference Include="System.Resources.ResourceManager" /> + <Reference Include="System.Runtime" /> + <Reference Include="System.Runtime.InteropServices" /> + <Reference Include="System.Threading" /> + <Reference Include="System.Threading.ThreadPool" /> + </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> </Project>
\ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/src/Configurations.props b/src/System.ServiceProcess.ServiceController/src/Configurations.props index 4e5de27086..ae3efb2d39 100644 --- a/src/System.ServiceProcess.ServiceController/src/Configurations.props +++ b/src/System.ServiceProcess.ServiceController/src/Configurations.props @@ -2,7 +2,7 @@ <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <BuildConfigurations> - netstandard-Windows_NT; + netcoreapp-Windows_NT; netstandard; netfx-Windows_NT; </BuildConfigurations> diff --git a/src/System.ServiceProcess.ServiceController/src/Resources/Strings.resx b/src/System.ServiceProcess.ServiceController/src/Resources/Strings.resx index 7111ac4072..270edcd771 100644 --- a/src/System.ServiceProcess.ServiceController/src/Resources/Strings.resx +++ b/src/System.ServiceProcess.ServiceController/src/Resources/Strings.resx @@ -100,4 +100,97 @@ <data name="PlatformNotSupported_ServiceController" xml:space="preserve"> <value>ServiceController enables manipulating and accessing Windows services and it is not applicable for other operating systems.</value> </data> + <data name="CannotChangeProperties" xml:space="preserve"> + <value>Cannot change CanStop, CanPauseAndContinue, CanShutdown, CanHandlePowerEvent, or CanHandleSessionChangeEvent property values after the service has been started.</value> + </data> + <data name="CannotChangeName" xml:space="preserve"> + <value>Cannot change service name when the service is running.</value> + </data> + <data name="ServiceName" xml:space="preserve"> + <value>Service name {0} contains invalid characters, is empty, or is too long (max length = {1}).</value> + </data> + <data name="NoServices" xml:space="preserve"> + <value>Service has not been supplied. At least one object derived from ServiceBase is required in order to run.</value> + </data> + <data name="CantStartFromCommandLine" xml:space="preserve"> + <value>Cannot start service from the command line or a debugger. A Windows Service must first be installed and then started with the ServerExplorer, Windows Services Administrative tool or the NET START command.</value> + </data> + <data name="NotInPendingState" xml:space="preserve"> + <value>UpdatePendingStatus can only be called during the handling of Start, Stop, Pause and Continue commands.</value> + </data> + <data name="StartSuccessful" xml:space="preserve"> + <value>Service started successfully.</value> + </data> + <data name="StopSuccessful" xml:space="preserve"> + <value>Service stopped successfully.</value> + </data> + <data name="PauseSuccessful" xml:space="preserve"> + <value>Service paused successfully.</value> + </data> + <data name="ContinueSuccessful" xml:space="preserve"> + <value>Service continued successfully.</value> + </data> + <data name="InstallSuccessful" xml:space="preserve"> + <value>Service was installed successfully.</value> + </data> + <data name="UninstallSuccessful" xml:space="preserve"> + <value>Service was uninstalled successfully.</value> + </data> + <data name="CommandSuccessful" xml:space="preserve"> + <value>Service command was processed successfully.</value> + </data> + <data name="StartFailed" xml:space="preserve"> + <value>Service cannot be started. {0}</value> + </data> + <data name="StopFailed" xml:space="preserve"> + <value>Failed to stop service. {0}</value> + </data> + <data name="PauseFailed" xml:space="preserve"> + <value>Failed to pause service. {0}</value> + </data> + <data name="ContinueFailed" xml:space="preserve"> + <value>Failed to continue service. {0}</value> + </data> + <data name="SessionChangeFailed" xml:space="preserve"> + <value>Failed to process session change. {0}</value> + </data> + <data name="InstallFailed" xml:space="preserve"> + <value>Failed to install service. Service may have been installed already.</value> + </data> + <data name="UninstallFailed" xml:space="preserve"> + <value>Failed to uninstall service. Service may be running.</value> + </data> + <data name="CommandFailed" xml:space="preserve"> + <value>Failed to process service command. {0}</value> + </data> + <data name="ErrorNumber" xml:space="preserve"> + <value>Windows Error number: {0}.</value> + </data> + <data name="ShutdownOK" xml:space="preserve"> + <value>Service has been successfully shut down.</value> + </data> + <data name="ShutdownFailed" xml:space="preserve"> + <value>Failed to shut down service. The error that occurred was: {0}.</value> + </data> + <data name="PowerEventOK" xml:space="preserve"> + <value>PowerEvent handled successfully by the service.</value> + </data> + <data name="PowerEventFailed" xml:space="preserve"> + <value>Failed in handling the PowerEvent. The error that occurred was: {0}.</value> + </data> + <data name="InstallOK" xml:space="preserve"> + <value>Service {0} has been successfully installed.</value> + </data> + <data name="TryToStop" xml:space="preserve"> + <value>Attempt to stop service {0}.</value> + </data> + <data name="ServiceRemoving" xml:space="preserve"> + <value>Service {0} is being removed from the system...</value> + </data> + <data name="ServiceRemoved" xml:space="preserve"> + <value>Service {0} was successfully removed from the system.</value> + </data> + <data name="ControlService" xml:space="preserve"> + <value>Cannot control {0} service on computer '{1}'.</value> + </data> </root>
\ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj b/src/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj index 1545cef32a..13348c1079 100644 --- a/src/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj +++ b/src/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj @@ -8,7 +8,7 @@ <ProjectGuid>{F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}</ProjectGuid> <IsPartialFacadeAssembly Condition="'$(TargetGroup)' == 'netfx'">true</IsPartialFacadeAssembly> <ResourcesSourceOutputDirectory Condition="'$(TargetGroup)' == 'netfx'">None</ResourcesSourceOutputDirectory> - <GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetGroup)' == 'netstandard' AND '$(TargetsWindows)' != 'true'">SR.PlatformNotSupported_ServiceController</GeneratePlatformNotSupportedAssemblyMessage> + <GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetGroup)' == 'netstandard'">SR.PlatformNotSupported_ServiceController</GeneratePlatformNotSupportedAssemblyMessage> <!-- Although we have a netstandard configuration, we know we are not currently UAP compatible--> <UWPCompatible>false</UWPCompatible> </PropertyGroup> @@ -18,7 +18,7 @@ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Release|AnyCPU'" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Debug|AnyCPU'" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Release|AnyCPU'" /> - <ItemGroup Condition="'$(TargetGroup)' == 'netstandard' AND '$(TargetsWindows)' == 'true'"> + <ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp' AND '$(TargetsWindows)' == 'true'"> <Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs"> <Link>Common\Interop\Windows\Interop.Libraries.cs</Link> </Compile> @@ -67,25 +67,54 @@ <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.SERVICE_STATUS.cs"> <Link>Common\Interop\Windows\Interop.SERVICE_STATUS.cs</Link> </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.SERVICE_TABLE_ENTRY.cs"> + <Link>Common\Interop\Windows\Interop.SERVICE_TABLE_ENTRY.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.ServiceControlDelegates.cs"> + <Link>Common\Interop\Windows\Interop.ServiceControlDelegates.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.SetServiceStatus.cs"> + <Link>Common\Interop\Windows\Interop.SetServiceStatus.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.WTSSESSION_NOTIFICATION.cs"> + <Link>Common\Interop\Windows\Interop.WTSSESSION_NOTIFICATION.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.RegisterServiceCtrlHandler.cs"> + <Link>Common\Interop\Windows\Interop.RegisterServiceCtrlHandler.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.RegisterServiceCtrlHandlerEx.cs"> + <Link>Common\Interop\Windows\Interop.RegisterServiceCtrlHandlerEx.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.StartServiceCtrlDispatcher.cs"> + <Link>Common\Interop\Windows\Interop.StartServiceCtrlDispatcher.cs</Link> + </Compile> <Compile Include="Microsoft\Win32\SafeHandles\SafeServiceHandle.cs" /> + <Compile Include="System\ServiceProcess\PowerBroadcastStatus.cs" /> + <Compile Include="System\ServiceProcess\ServiceBase.cs" /> <Compile Include="System\ServiceProcess\ServiceController.cs" /> <Compile Include="System\ServiceProcess\ServiceControllerStatus.cs" /> <Compile Include="System\ServiceProcess\ServiceStartMode.cs" /> <Compile Include="System\ServiceProcess\ServiceType.cs" /> + <Compile Include="System\ServiceProcess\SessionChangeDescription.cs" /> + <Compile Include="System\ServiceProcess\SessionChangeReason.cs" /> <Compile Include="System\ServiceProcess\TimeoutException.cs" /> </ItemGroup> <ItemGroup Condition="'$(TargetGroup)' == 'netfx'"> <Reference Include="mscorlib" /> <Reference Include="System.ServiceProcess" /> </ItemGroup> - <ItemGroup Condition="'$(TargetGroup)' != 'netfx'"> + <ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'"> <Reference Include="Microsoft.Win32.Primitives" /> <Reference Include="System.Collections" /> + <Reference Include="System.Console" /> + <Reference Include="System.ComponentModel.Primitives" /> + <Reference Include="System.Diagnostics.Debug" /> <Reference Include="System.Diagnostics.Tools" /> <Reference Include="System.Resources.ResourceManager" /> <Reference Include="System.Runtime" /> <Reference Include="System.Runtime.InteropServices" /> <Reference Include="System.Threading" /> + <Reference Include="System.Threading.ThreadPool" /> </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> </Project>
\ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/PowerBroadcastStatus.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/PowerBroadcastStatus.cs new file mode 100644 index 0000000000..5d4b09b9a2 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/PowerBroadcastStatus.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ServiceProcess +{ + public enum PowerBroadcastStatus + { + BatteryLow = Interop.Advapi32.PowerBroadcastStatus.PBT_APMBATTERYLOW, + OemEvent = Interop.Advapi32.PowerBroadcastStatus.PBT_APMOEMEVENT, + PowerStatusChange = Interop.Advapi32.PowerBroadcastStatus.PBT_APMPOWERSTATUSCHANGE, + QuerySuspend = Interop.Advapi32.PowerBroadcastStatus.PBT_APMQUERYSUSPEND, + QuerySuspendFailed = Interop.Advapi32.PowerBroadcastStatus.PBT_APMQUERYSUSPENDFAILED, + ResumeAutomatic = Interop.Advapi32.PowerBroadcastStatus.PBT_APMRESUMEAUTOMATIC, + ResumeCritical = Interop.Advapi32.PowerBroadcastStatus.PBT_APMRESUMECRITICAL, + ResumeSuspend = Interop.Advapi32.PowerBroadcastStatus.PBT_APMRESUMESUSPEND, + Suspend = Interop.Advapi32.PowerBroadcastStatus.PBT_APMSUSPEND, + } +} + diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs new file mode 100644 index 0000000000..d4be605cd8 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs @@ -0,0 +1,898 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security; +using System.Threading; + +using static Interop.Advapi32; + +namespace System.ServiceProcess +{ + /// <devdoc> + /// <para>Provides a base class for a service that will exist as part of a service application. <see cref='System.ServiceProcess.ServiceBase'/> + /// must be derived when creating a new service class.</para> + /// </devdoc> + public class ServiceBase : Component + { + private SERVICE_STATUS _status = new SERVICE_STATUS(); + private IntPtr _statusHandle; + private ServiceControlCallback _commandCallback; + private ServiceControlCallbackEx _commandCallbackEx; + private ServiceMainCallback _mainCallback; + private IntPtr _handleName; + private ManualResetEvent _startCompletedSignal; + private int _acceptedCommands; + private string _serviceName; + private bool _nameFrozen; // set to true once we've started running and ServiceName can't be changed any more. + private bool _commandPropsFrozen; // set to true once we've use the Can... properties. + private bool _disposed; + private bool _initialized; + + /// <devdoc> + /// <para> + /// Indicates the maximum size for a service name. + /// </para> + /// </devdoc> + public const int MaxNameLength = 80; + + /// <devdoc> + /// <para>Creates a new instance of the <see cref='System.ServiceProcess.ServiceBase()'/> class.</para> + /// </devdoc> + public ServiceBase() + { + _acceptedCommands = AcceptOptions.ACCEPT_STOP; + ServiceName = ""; + } + + /// <devdoc> + /// When this method is called from OnStart, OnStop, OnPause or OnContinue, + /// the specified wait hint is passed to the + /// Service Control Manager to avoid having the service marked as hung. + /// </devdoc> + public unsafe void RequestAdditionalTime(int milliseconds) + { + fixed (SERVICE_STATUS* pStatus = &_status) + { + if (_status.currentState != ServiceControlStatus.STATE_CONTINUE_PENDING && + _status.currentState != ServiceControlStatus.STATE_START_PENDING && + _status.currentState != ServiceControlStatus.STATE_STOP_PENDING && + _status.currentState != ServiceControlStatus.STATE_PAUSE_PENDING) + { + throw new InvalidOperationException(SR.NotInPendingState); + } + + _status.waitHint = milliseconds; + _status.checkPoint++; + SetServiceStatus(_statusHandle, pStatus); + } + } + + /// <devdoc> + /// The termination code for the service. Set this to a non-zero value before + /// stopping to indicate an error to the Service Control Manager. + /// </devdoc> + public int ExitCode + { + get + { + return _status.win32ExitCode; + } + set + { + _status.win32ExitCode = value; + } + } + + /// <devdoc> + /// Indicates whether the service can be handle notifications on + /// computer power status changes. + /// </devdoc> + [DefaultValue(false)] + public bool CanHandlePowerEvent + { + get + { + return (_acceptedCommands & AcceptOptions.ACCEPT_POWEREVENT) != 0; + } + set + { + if (_commandPropsFrozen) + throw new InvalidOperationException(SR.CannotChangeProperties); + + if (value) + { + _acceptedCommands |= AcceptOptions.ACCEPT_POWEREVENT; + } + else + { + _acceptedCommands &= ~AcceptOptions.ACCEPT_POWEREVENT; + } + } + } + + /// <devdoc> + /// Indicates whether the service can handle Terminal Server session change events. + /// </devdoc> + [DefaultValue(false)] + public bool CanHandleSessionChangeEvent + { + get + { + return (_acceptedCommands & AcceptOptions.ACCEPT_SESSIONCHANGE) != 0; + } + set + { + if (_commandPropsFrozen) + throw new InvalidOperationException(SR.CannotChangeProperties); + + if (value) + { + _acceptedCommands |= AcceptOptions.ACCEPT_SESSIONCHANGE; + } + else + { + _acceptedCommands &= ~AcceptOptions.ACCEPT_SESSIONCHANGE; + } + } + } + + /// <devdoc> + /// <para> Indicates whether the service can be paused + /// and resumed.</para> + /// </devdoc> + [DefaultValue(false)] + public bool CanPauseAndContinue + { + get + { + return (_acceptedCommands & AcceptOptions.ACCEPT_PAUSE_CONTINUE) != 0; + } + set + { + if (_commandPropsFrozen) + throw new InvalidOperationException(SR.CannotChangeProperties); + + if (value) + { + _acceptedCommands |= AcceptOptions.ACCEPT_PAUSE_CONTINUE; + } + else + { + _acceptedCommands &= ~AcceptOptions.ACCEPT_PAUSE_CONTINUE; + } + } + } + + /// <devdoc> + /// <para> Indicates whether the service should be notified when + /// the system is shutting down.</para> + /// </devdoc> + [DefaultValue(false)] + public bool CanShutdown + { + get + { + return (_acceptedCommands & AcceptOptions.ACCEPT_SHUTDOWN) != 0; + } + set + { + if (_commandPropsFrozen) + throw new InvalidOperationException(SR.CannotChangeProperties); + + if (value) + { + _acceptedCommands |= AcceptOptions.ACCEPT_SHUTDOWN; + } + else + { + _acceptedCommands &= ~AcceptOptions.ACCEPT_SHUTDOWN; + } + } + } + + /// <devdoc> + /// <para> Indicates whether the service can be + /// stopped once it has started.</para> + /// </devdoc> + [DefaultValue(true)] + public bool CanStop + { + get + { + return (_acceptedCommands & AcceptOptions.ACCEPT_STOP) != 0; + } + set + { + if (_commandPropsFrozen) + throw new InvalidOperationException(SR.CannotChangeProperties); + + if (value) + { + _acceptedCommands |= AcceptOptions.ACCEPT_STOP; + } + else + { + _acceptedCommands &= ~AcceptOptions.ACCEPT_STOP; + } + } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected IntPtr ServiceHandle + { + get + { + return _statusHandle; + } + } + + /// <devdoc> + /// <para> Indicates the short name used to identify the service to the system.</para> + /// </devdoc> + public string ServiceName + { + get + { + return _serviceName; + } + set + { + if (_nameFrozen) + throw new InvalidOperationException(SR.CannotChangeName); + + // For component properties, "" is a special case. + if (value != "" && !ValidServiceName(value)) + throw new ArgumentException(SR.Format(SR.ServiceName, value, ServiceBase.MaxNameLength.ToString(CultureInfo.CurrentCulture))); + + _serviceName = value; + } + } + + internal static bool ValidServiceName(string serviceName) + { + if (serviceName == null) + return false; + + // not too long and check for empty name as well. + if (serviceName.Length > ServiceBase.MaxNameLength || serviceName.Length == 0) + return false; + + // no slashes or backslash allowed + foreach (char c in serviceName) + { + if ((c == '\\') || (c == '/')) + return false; + } + + return true; + } + + /// <devdoc> + /// <para>Disposes of the resources (other than memory ) used by + /// the <see cref='System.ServiceProcess.ServiceBase'/>.</para> + /// </devdoc> + protected override void Dispose(bool disposing) + { + if (_handleName != (IntPtr)0) + { + Marshal.FreeHGlobal(_handleName); + _handleName = (IntPtr)0; + } + + _nameFrozen = false; + _commandPropsFrozen = false; + _disposed = true; + base.Dispose(disposing); + } + + /// <devdoc> + /// <para> When implemented in a + /// derived class, + /// executes when a Continue command is sent to the service + /// by the + /// Service Control Manager. Specifies the actions to take when a + /// service resumes normal functioning after being paused.</para> + /// </devdoc> + protected virtual void OnContinue() + { + } + + /// <devdoc> + /// <para> When implemented in a + /// derived class, executes when a Pause command is sent + /// to + /// the service by the Service Control Manager. Specifies the + /// actions to take when a service pauses.</para> + /// </devdoc> + protected virtual void OnPause() + { + } + + /// <devdoc> + /// <para> + /// When implemented in a derived class, executes when the computer's + /// power status has changed. + /// </para> + /// </devdoc> + protected virtual bool OnPowerEvent(PowerBroadcastStatus powerStatus) + { + return true; + } + + /// <devdoc> + /// <para>When implemented in a derived class, + /// executes when a Terminal Server session change event is received.</para> + /// </devdoc> + protected virtual void OnSessionChange(SessionChangeDescription changeDescription) + { + } + + /// <devdoc> + /// <para>When implemented in a derived class, + /// executes when the system is shutting down. + /// Specifies what should + /// happen just prior + /// to the system shutting down.</para> + /// </devdoc> + protected virtual void OnShutdown() + { + } + + /// <devdoc> + /// <para> When implemented in a + /// derived class, executes when a Start command is sent + /// to the service by the Service + /// Control Manager. Specifies the actions to take when the service starts.</para> + /// <note type="rnotes"> + /// Tech review note: + /// except that the SCM does not allow passing arguments, so this overload will + /// never be called by the SCM in the current version. Question: Is this true even + /// when the string array is empty? What should we say, then. Can + /// a ServiceBase derived class only be called programmatically? Will + /// OnStart never be called if you use the SCM to start the service? What about + /// services that start automatically at boot-up? + /// </note> + /// </devdoc> + protected virtual void OnStart(string[] args) + { + } + + /// <devdoc> + /// <para> When implemented in a + /// derived class, executes when a Stop command is sent to the + /// service by the Service Control Manager. Specifies the actions to take when a + /// service stops + /// running.</para> + /// </devdoc> + protected virtual void OnStop() + { + } + + private unsafe void DeferredContinue() + { + fixed (SERVICE_STATUS* pStatus = &_status) + { + try + { + OnContinue(); + WriteLogEntry(SR.ContinueSuccessful); + _status.currentState = ServiceControlStatus.STATE_RUNNING; + } + catch (Exception e) + { + _status.currentState = ServiceControlStatus.STATE_PAUSED; + WriteLogEntry(SR.Format(SR.ContinueFailed, e.ToString()), true); + + // We re-throw the exception so that the advapi32 code can report + // ERROR_EXCEPTION_IN_SERVICE as it would for native services. + throw; + } + finally + { + SetServiceStatus(_statusHandle, pStatus); + } + } + } + + private void DeferredCustomCommand(int command) + { + try + { + OnCustomCommand(command); + WriteLogEntry(SR.CommandSuccessful); + } + catch (Exception e) + { + WriteLogEntry(SR.Format(SR.CommandFailed, e.ToString()), true); + + // We should re-throw the exception so that the advapi32 code can report + // ERROR_EXCEPTION_IN_SERVICE as it would for native services. + throw; + } + } + + private unsafe void DeferredPause() + { + fixed (SERVICE_STATUS* pStatus = &_status) + { + try + { + OnPause(); + WriteLogEntry(SR.PauseSuccessful); + _status.currentState = ServiceControlStatus.STATE_PAUSED; + } + catch (Exception e) + { + _status.currentState = ServiceControlStatus.STATE_RUNNING; + WriteLogEntry(SR.Format(SR.PauseFailed, e.ToString()), true); + + // We re-throw the exception so that the advapi32 code can report + // ERROR_EXCEPTION_IN_SERVICE as it would for native services. + throw; + } + finally + { + SetServiceStatus(_statusHandle, pStatus); + } + } + } + + private void DeferredPowerEvent(int eventType, IntPtr eventData) + { + // Note: The eventData pointer might point to an invalid location + // This might happen because, between the time the eventData ptr was + // captured and the time this deferred code runs, the ptr might have + // already been freed. + try + { + PowerBroadcastStatus status = (PowerBroadcastStatus)eventType; + bool statusResult = OnPowerEvent(status); + + WriteLogEntry(SR.PowerEventOK); + } + catch (Exception e) + { + WriteLogEntry(SR.Format(SR.PowerEventFailed, e.ToString()), true); + + // We rethrow the exception so that advapi32 code can report + // ERROR_EXCEPTION_IN_SERVICE as it would for native services. + throw; + } + } + + private void DeferredSessionChange(int eventType, int sessionId) + { + try + { + OnSessionChange(new SessionChangeDescription((SessionChangeReason)eventType, sessionId)); + } + catch (Exception e) + { + WriteLogEntry(SR.Format(SR.SessionChangeFailed, e.ToString()), true); + + // We rethrow the exception so that advapi32 code can report + // ERROR_EXCEPTION_IN_SERVICE as it would for native services. + throw; + } + } + + // We mustn't call OnStop directly from the command callback, as this will + // tie up the command thread for the duration of the OnStop, which can be lengthy. + // This is a problem when multiple services are hosted in a single process. + private unsafe void DeferredStop() + { + fixed (SERVICE_STATUS* pStatus = &_status) + { + int previousState = _status.currentState; + + _status.checkPoint = 0; + _status.waitHint = 0; + _status.currentState = ServiceControlStatus.STATE_STOP_PENDING; + SetServiceStatus(_statusHandle, pStatus); + try + { + OnStop(); + WriteLogEntry(SR.StopSuccessful); + _status.currentState = ServiceControlStatus.STATE_STOPPED; + SetServiceStatus(_statusHandle, pStatus); + } + catch (Exception e) + { + _status.currentState = previousState; + SetServiceStatus(_statusHandle, pStatus); + WriteLogEntry(SR.Format(SR.StopFailed, e.ToString()), true); + throw; + } + } + } + + private unsafe void DeferredShutdown() + { + try + { + OnShutdown(); + WriteLogEntry(SR.Format(SR.ShutdownOK)); + + if (_status.currentState == ServiceControlStatus.STATE_PAUSED || _status.currentState == ServiceControlStatus.STATE_RUNNING) + { + fixed (SERVICE_STATUS* pStatus = &_status) + { + _status.checkPoint = 0; + _status.waitHint = 0; + _status.currentState = ServiceControlStatus.STATE_STOPPED; + SetServiceStatus(_statusHandle, pStatus); + } + } + } + catch (Exception e) + { + WriteLogEntry(SR.Format(SR.ShutdownFailed, e.ToString()), true); + throw; + } + } + + /// <devdoc> + /// <para>When implemented in a derived class, <see cref='System.ServiceProcess.ServiceBase.OnCustomCommand'/> + /// executes when a custom command is passed to + /// the service. Specifies the actions to take when + /// a command with the specified parameter value occurs.</para> + /// <note type="rnotes"> + /// Previously had "Passed to the + /// service by + /// the SCM", but the SCM doesn't pass custom commands. Do we want to indicate an + /// agent here? Would it be the ServiceController, or is there another way to pass + /// the int into the service? I thought that the SCM did pass it in, but + /// otherwise ignored it since it was an int it doesn't recognize. I was under the + /// impression that the difference was that the SCM didn't have default processing, so + /// it transmitted it without examining it or trying to performs its own + /// default behavior on it. Please correct where my understanding is wrong in the + /// second paragraph below--what, if any, contact does the SCM have with a + /// custom command? + /// </note> + /// </devdoc> + protected virtual void OnCustomCommand(int command) + { + } + + /// <devdoc> + /// <para>Provides the main entry point for an executable that + /// contains multiple associated services. Loads the specified services into memory so they can be + /// started.</para> + /// </devdoc> + public static void Run(ServiceBase[] services) + { + if (services == null || services.Length == 0) + throw new ArgumentException(SR.NoServices); + + IntPtr entriesPointer = Marshal.AllocHGlobal((IntPtr)((services.Length + 1) * Marshal.SizeOf(typeof(SERVICE_TABLE_ENTRY)))); + SERVICE_TABLE_ENTRY[] entries = new SERVICE_TABLE_ENTRY[services.Length]; + bool multipleServices = services.Length > 1; + IntPtr structPtr = (IntPtr)0; + + for (int index = 0; index < services.Length; ++index) + { + services[index].Initialize(multipleServices); + entries[index] = services[index].GetEntry(); + structPtr = (IntPtr)((long)entriesPointer + Marshal.SizeOf(typeof(SERVICE_TABLE_ENTRY)) * index); + Marshal.StructureToPtr(entries[index], structPtr, true); + } + + SERVICE_TABLE_ENTRY lastEntry = new SERVICE_TABLE_ENTRY(); + + lastEntry.callback = null; + lastEntry.name = (IntPtr)0; + structPtr = (IntPtr)((long)entriesPointer + Marshal.SizeOf(typeof(SERVICE_TABLE_ENTRY)) * services.Length); + Marshal.StructureToPtr(lastEntry, structPtr, true); + + // While the service is running, this function will never return. It will return when the service + // is stopped. + bool res = StartServiceCtrlDispatcher(entriesPointer); + string errorMessage = ""; + + if (!res) + { + errorMessage = new Win32Exception().Message; + Console.WriteLine(SR.CantStartFromCommandLine); + } + + foreach (ServiceBase service in services) + { + service.Dispose(); + if (!res) + { + service.WriteLogEntry(SR.Format(SR.StartFailed, errorMessage), true); + } + } + } + + /// <devdoc> + /// <para>Provides the main + /// entry point for an executable that contains a single + /// service. Loads the service into memory so it can be + /// started.</para> + /// </devdoc> + public static void Run(ServiceBase service) + { + if (service == null) + throw new ArgumentException(SR.NoServices); + + Run(new ServiceBase[] { service }); + } + + public void Stop() + { + DeferredStop(); + } + + private void Initialize(bool multipleServices) + { + if (!_initialized) + { + //Cannot register the service with NT service manatger if the object has been disposed, since finalization has been suppressed. + if (_disposed) + throw new ObjectDisposedException(GetType().Name); + + if (!multipleServices) + { + _status.serviceType = ServiceTypeOptions.SERVICE_TYPE_WIN32_OWN_PROCESS; + } + else + { + _status.serviceType = ServiceTypeOptions.SERVICE_TYPE_WIN32_SHARE_PROCESS; + } + + _status.currentState = ServiceControlStatus.STATE_START_PENDING; + _status.controlsAccepted = 0; + _status.win32ExitCode = 0; + _status.serviceSpecificExitCode = 0; + _status.checkPoint = 0; + _status.waitHint = 0; + + _mainCallback = new ServiceMainCallback(this.ServiceMainCallback); + _commandCallback = new ServiceControlCallback(this.ServiceCommandCallback); + _commandCallbackEx = new ServiceControlCallbackEx(this.ServiceCommandCallbackEx); + _handleName = Marshal.StringToHGlobalUni(this.ServiceName); + + _initialized = true; + } + } + + private SERVICE_TABLE_ENTRY GetEntry() + { + SERVICE_TABLE_ENTRY entry = new SERVICE_TABLE_ENTRY(); + + _nameFrozen = true; + entry.callback = (Delegate)_mainCallback; + entry.name = _handleName; + return entry; + } + + private int ServiceCommandCallbackEx(int command, int eventType, IntPtr eventData, IntPtr eventContext) + { + switch (command) + { + case ControlOptions.CONTROL_POWEREVENT: + { + ThreadPool.QueueUserWorkItem(_ => DeferredPowerEvent(eventType, eventData)); + break; + } + + case ControlOptions.CONTROL_SESSIONCHANGE: + { + // The eventData pointer can be released between now and when the DeferredDelegate gets called. + // So we capture the session id at this point + WTSSESSION_NOTIFICATION sessionNotification = new WTSSESSION_NOTIFICATION(); + Marshal.PtrToStructure(eventData, sessionNotification); + ThreadPool.QueueUserWorkItem(_ => DeferredSessionChange(eventType, sessionNotification.sessionId)); + break; + } + + default: + { + ServiceCommandCallback(command); + break; + } + } + + return 0; + } + + /// <devdoc> + /// Command Handler callback is called by NT . + /// Need to take specific action in response to each + /// command message. There is usually no need to override this method. + /// Instead, override OnStart, OnStop, OnCustomCommand, etc. + /// </devdoc> + /// <internalonly/> + private unsafe void ServiceCommandCallback(int command) + { + fixed (SERVICE_STATUS* pStatus = &_status) + { + if (command == ControlOptions.CONTROL_INTERROGATE) + SetServiceStatus(_statusHandle, pStatus); + else if (_status.currentState != ServiceControlStatus.STATE_CONTINUE_PENDING && + _status.currentState != ServiceControlStatus.STATE_START_PENDING && + _status.currentState != ServiceControlStatus.STATE_STOP_PENDING && + _status.currentState != ServiceControlStatus.STATE_PAUSE_PENDING) + { + switch (command) + { + case ControlOptions.CONTROL_CONTINUE: + if (_status.currentState == ServiceControlStatus.STATE_PAUSED) + { + _status.currentState = ServiceControlStatus.STATE_CONTINUE_PENDING; + SetServiceStatus(_statusHandle, pStatus); + + ThreadPool.QueueUserWorkItem(_ => DeferredContinue()); + } + + break; + + case ControlOptions.CONTROL_PAUSE: + if (_status.currentState == ServiceControlStatus.STATE_RUNNING) + { + _status.currentState = ServiceControlStatus.STATE_PAUSE_PENDING; + SetServiceStatus(_statusHandle, pStatus); + + ThreadPool.QueueUserWorkItem(_ => DeferredPause()); + } + + break; + + case ControlOptions.CONTROL_STOP: + int previousState = _status.currentState; + // + // Can't perform all of the service shutdown logic from within the command callback. + // This is because there is a single ScDispatcherLoop for the entire process. Instead, we queue up an + // asynchronous call to "DeferredStop", and return immediately. This is crucial for the multiple service + // per process scenario, such as the new managed service host model. + // + if (_status.currentState == ServiceControlStatus.STATE_PAUSED || _status.currentState == ServiceControlStatus.STATE_RUNNING) + { + _status.currentState = ServiceControlStatus.STATE_STOP_PENDING; + SetServiceStatus(_statusHandle, pStatus); + // Set our copy of the state back to the previous so that the deferred stop routine + // can also save the previous state. + _status.currentState = previousState; + + ThreadPool.QueueUserWorkItem(_ => DeferredStop()); + } + + break; + + case ControlOptions.CONTROL_SHUTDOWN: + // + // Same goes for shutdown -- this needs to be very responsive, so we can't have one service tying up the + // dispatcher loop. + // + ThreadPool.QueueUserWorkItem(_ => DeferredShutdown()); + break; + + default: + ThreadPool.QueueUserWorkItem(_ => DeferredCustomCommand(command)); + break; + } + } + } + } + + // Need to execute the start method on a thread pool thread. + // Most applications will start asynchronous operations in the + // OnStart method. If such a method is executed in MainCallback + // thread, the async operations might get canceled immediately. + private void ServiceQueuedMainCallback(object state) + { + string[] args = (string[])state; + + try + { + OnStart(args); + WriteLogEntry(SR.StartSuccessful); + _status.checkPoint = 0; + _status.waitHint = 0; + _status.currentState = ServiceControlStatus.STATE_RUNNING; + } + catch (Exception e) + { + WriteLogEntry(SR.Format(SR.StartFailed, e.ToString()), true); + _status.currentState = ServiceControlStatus.STATE_STOPPED; + } + _startCompletedSignal.Set(); + } + + /// <devdoc> + /// ServiceMain callback is called by NT . + /// It is expected that we register the command handler, + /// and start the service at this point. + /// </devdoc> + /// <internalonly/> + [EditorBrowsable(EditorBrowsableState.Never)] + public unsafe void ServiceMainCallback(int argCount, IntPtr argPointer) + { + fixed (SERVICE_STATUS* pStatus = &_status) + { + string[] args = null; + + if (argCount > 0) + { + char** argsAsPtr = (char**)argPointer.ToPointer(); + + //Lets read the arguments + // the first arg is always the service name. We don't want to pass that in. + args = new string[argCount - 1]; + + for (int index = 0; index < args.Length; ++index) + { + // we increment the pointer first so we skip over the first argument. + argsAsPtr++; + args[index] = Marshal.PtrToStringUni((IntPtr)(*argsAsPtr)); + } + } + + // If we are being hosted, then Run will not have been called, since the EXE's Main entrypoint is not called. + if (!_initialized) + { + Initialize(true); + } + + _statusHandle = RegisterServiceCtrlHandlerEx(ServiceName, (Delegate)_commandCallbackEx, (IntPtr)0); + + _nameFrozen = true; + if (_statusHandle == (IntPtr)0) + { + string errorMessage = new Win32Exception().Message; + WriteLogEntry(SR.Format(SR.StartFailed, errorMessage), true); + } + + _status.controlsAccepted = _acceptedCommands; + _commandPropsFrozen = true; + if ((_status.controlsAccepted & AcceptOptions.ACCEPT_STOP) != 0) + { + _status.controlsAccepted = _status.controlsAccepted | AcceptOptions.ACCEPT_SHUTDOWN; + } + + _status.currentState = ServiceControlStatus.STATE_START_PENDING; + + bool statusOK = SetServiceStatus(_statusHandle, pStatus); + + if (!statusOK) + { + return; + } + + // Need to execute the start method on a thread pool thread. + // Most applications will start asynchronous operations in the + // OnStart method. If such a method is executed in the current + // thread, the async operations might get canceled immediately + // since NT will terminate this thread right after this function + // finishes. + _startCompletedSignal = new ManualResetEvent(false); + ThreadPool.QueueUserWorkItem(new WaitCallback(this.ServiceQueuedMainCallback), args); + _startCompletedSignal.WaitOne(); + statusOK = SetServiceStatus(_statusHandle, pStatus); + if (!statusOK) + { + WriteLogEntry(SR.Format(SR.StartFailed, new Win32Exception().Message), true); + _status.currentState = ServiceControlStatus.STATE_STOPPED; + SetServiceStatus(_statusHandle, pStatus); + } + } + } + + private void WriteLogEntry(string message, bool error = false) + { + // Used to write to EventLog but for now just logging to debug output stream + // might want to plumb other logging in the future. + + Debug.WriteLine((error ? "Error: " : "") + message); + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs index dace4866a0..0dabc4e9cc 100644 --- a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs @@ -3,23 +3,23 @@ // See the LICENSE file in the project root for more information. using Microsoft.Win32.SafeHandles; -using System.Text; -using System.Runtime.InteropServices; -using System.ComponentModel; -using System.Diagnostics; using System; using System.Collections.Generic; -using System.Threading; +using System.ComponentModel; +using System.Diagnostics; using System.Globalization; +using System.Runtime.InteropServices; using System.Security; +using System.Text; +using System.Threading; namespace System.ServiceProcess { /// This class represents an NT service. It allows you to connect to a running or stopped service /// and manipulate it or get information about it. - public class ServiceController : IDisposable + public class ServiceController : Component { - private readonly string _machineName; + private string _machineName; private readonly ManualResetEvent _waitForStatusSignal = new ManualResetEvent(false); private const string DefaultMachineName = "."; @@ -40,6 +40,11 @@ namespace System.ServiceProcess private const int SERVICENAMEMAXLENGTH = 80; private const int DISPLAYNAMEBUFFERSIZE = 256; + public ServiceController() + { + _type = Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_ALL; + } + /// Creates a ServiceController object, based on service name. public ServiceController(string name) : this(name, DefaultMachineName) @@ -132,6 +137,22 @@ namespace System.ServiceProcess GenerateNames(); return _displayName; } + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (string.Equals(value, _displayName, StringComparison.OrdinalIgnoreCase)) + { + // they're just changing the casing. No need to close. + _displayName = value; + return; + } + + Close(); + _displayName = value; + _name = ""; + } } /// The set of services that depend on this service. These are the services that will be stopped if @@ -203,6 +224,22 @@ namespace System.ServiceProcess { return _machineName; } + set + { + if (!CheckMachineName(value)) + throw new ArgumentException(SR.Format(SR.BadMachineName, value)); + + if (string.Equals(_machineName, value, StringComparison.OrdinalIgnoreCase)) + { + // no need to close, because the most they're changing is the + // casing. + _machineName = value; + return; + } + + Close(); + _machineName = value; + } } /// Returns the short name of the service referenced by this object. @@ -214,6 +251,26 @@ namespace System.ServiceProcess GenerateNames(); return _name; } + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (string.Equals(value, _name, StringComparison.OrdinalIgnoreCase)) + { + // they might be changing the casing, but the service we refer to + // is the same. No need to close. + _name = value; + return; + } + + if (!ServiceBase.ValidServiceName(value)) + throw new ArgumentException(SR.Format(SR.ServiceName, value, ServiceBase.MaxNameLength.ToString(CultureInfo.CurrentCulture))); + + Close(); + _name = value; + _displayName = ""; + } } public unsafe ServiceController[] ServicesDependedOn @@ -254,7 +311,7 @@ namespace System.ServiceProcess if (dependencyChar != null) { // lpDependencies points to the start of multiple null-terminated strings. The list is - // double-null terminated. + // double-null terminated. int length = 0; dependencyHash = new Dictionary<string, ServiceController>(); while (*(dependencyChar + length) != '\0') @@ -391,17 +448,13 @@ namespace System.ServiceProcess return !string.IsNullOrWhiteSpace(value) && value.IndexOf('\\') == -1; } - private void Close() + public void Close() { - } - - public void Dispose() - { - Dispose(true); + Dispose(); } /// Disconnects this object from the service and frees any allocated resources. - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { if (_serviceManagerHandle != null) { @@ -723,6 +776,25 @@ namespace System.ServiceProcess } } + public unsafe void ExecuteCommand(int command) + { + IntPtr serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_USER_DEFINED_CONTROL); + try + { + Interop.Advapi32.SERVICE_STATUS status = new Interop.Advapi32.SERVICE_STATUS(); + bool result = Interop.Advapi32.ControlService(serviceHandle, command, &status); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.ControlService, ServiceName, MachineName), inner); + } + } + finally + { + Interop.Advapi32.CloseServiceHandle(serviceHandle); + } + } + /// Refreshes all property values. public void Refresh() { diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs new file mode 100644 index 0000000000..8889f18031 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ServiceProcess +{ + public struct SessionChangeDescription + { + private readonly SessionChangeReason _reason; + private readonly int _id; + + internal SessionChangeDescription(SessionChangeReason reason, int id) + { + _reason = reason; + _id = id; + } + + public SessionChangeReason Reason + { + get + { + return _reason; + } + } + + public int SessionId + { + get + { + return _id; + } + } + + public override bool Equals(object obj) + { + if (obj == null || !(obj is SessionChangeDescription)) + { + return false; + } + else + { + return Equals((SessionChangeDescription)obj); + } + } + + public override int GetHashCode() + { + return (int)_reason ^ _id; + } + + public bool Equals(SessionChangeDescription changeDescription) + { + return (_reason == changeDescription._reason) && (_id == changeDescription._id); + } + + public static bool operator ==(SessionChangeDescription a, SessionChangeDescription b) + { + return a.Equals(b); + } + + public static bool operator !=(SessionChangeDescription a, SessionChangeDescription b) + { + return !a.Equals(b); + } + } +} + diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs new file mode 100644 index 0000000000..b3c3ee1026 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ServiceProcess +{ + public enum SessionChangeReason + { + /// <devdoc> + /// <para>A session was connected to the console session. </para> + /// </devdoc> + ConsoleConnect = Interop.Advapi32.SessionStateChange.WTS_CONSOLE_CONNECT, + /// <devdoc> + /// <para>A session was disconnected from the console session. </para> + /// </devdoc> + ConsoleDisconnect = Interop.Advapi32.SessionStateChange.WTS_CONSOLE_DISCONNECT, + /// <devdoc> + /// <para>A session was connected to the remote session. </para> + /// </devdoc> + RemoteConnect = Interop.Advapi32.SessionStateChange.WTS_REMOTE_CONNECT, + /// <devdoc> + /// <para>A session was disconnected from the remote session. </para> + /// </devdoc> + RemoteDisconnect = Interop.Advapi32.SessionStateChange.WTS_REMOTE_DISCONNECT, + /// <devdoc> + /// <para>A user has logged on to the session. </para> + /// </devdoc> + SessionLogon = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOGON, + /// <devdoc> + /// <para>A user has logged off the session. </para> + /// </devdoc> + SessionLogoff = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOGOFF, + /// <devdoc> + /// <para>A session has been locked. </para> + /// </devdoc> + SessionLock = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOCK, + /// <devdoc> + /// <para>A session has been unlocked. </para> + /// </devdoc> + SessionUnlock = Interop.Advapi32.SessionStateChange.WTS_SESSION_UNLOCK, + /// <devdoc> + /// <para>A session has changed its remote controlled status. </para> + /// </devdoc> + SessionRemoteControl = Interop.Advapi32.SessionStateChange.WTS_SESSION_REMOTE_CONTROL + } +} + diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs index 600d37744c..2b71e989e5 100644 --- a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs @@ -7,7 +7,7 @@ using System.Runtime.Serialization; namespace System.ServiceProcess { - public class TimeoutException : Exception + public class TimeoutException : SystemException { private const int ServiceControllerTimeout = unchecked((int)0x80131906); diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/Configurations.props b/src/System.ServiceProcess.ServiceController/tests/Configurations.props index 249c8c18b4..552f5e92bc 100644 --- a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/Configurations.props +++ b/src/System.ServiceProcess.ServiceController/tests/Configurations.props @@ -2,7 +2,8 @@ <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <BuildConfigurations> - netstandard-Windows_NT; + netcoreapp-Windows_NT; + netfx-Windows_NT; </BuildConfigurations> </PropertyGroup> </Project>
\ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/SafeServiceControllerTests.cs b/src/System.ServiceProcess.ServiceController/tests/SafeServiceControllerTests.cs index 1cb0515ef8..c9ffab58bf 100644 --- a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/SafeServiceControllerTests.cs +++ b/src/System.ServiceProcess.ServiceController/tests/SafeServiceControllerTests.cs @@ -6,7 +6,6 @@ using Xunit; namespace System.ServiceProcess.Tests { - [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "Appx doesn't allow to access ServiceController")] public static class SafeServiceControllerTests { private const string KeyIsoSvcName = "KEYISO"; @@ -17,7 +16,7 @@ namespace System.ServiceProcess.Tests bool foundKeyIsoSvc = false; bool foundSamSvc = false; bool foundOtherSvc = false; - + foreach (var service in ServiceController.GetServices()) { // The CNG Key Isolation service (KeyIso) and Security Accounts Manager (SAM) service (SamSs) diff --git a/src/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs b/src/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs new file mode 100644 index 0000000000..db05035a82 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using System; +using System.Diagnostics; +using System.Security.Principal; +using Xunit; + +/// <summary> +/// NOTE: All tests checking the output file should always call Stop before checking because Stop will flush the file to disk. +/// </summary> +namespace System.ServiceProcess.Tests +{ + [OuterLoop(/* Modifies machine state */)] + public class ServiceBaseTests : IDisposable + { + private readonly TestServiceProvider _testService; + + public ServiceBaseTests() + { + _testService = new TestServiceProvider(); + } + + private static bool RunningWithElevatedPrivileges + { + get { return TestServiceProvider.RunningWithElevatedPrivileges; } + } + private void AssertExpectedProperties(ServiceController testServiceController) + { + var comparer = PlatformDetection.IsFullFramework ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; // Full framework upper cases the name + Assert.Equal(_testService.TestServiceName, testServiceController.ServiceName, comparer); + Assert.Equal(_testService.TestServiceDisplayName, testServiceController.DisplayName); + Assert.Equal(_testService.TestMachineName, testServiceController.MachineName); + Assert.Equal(ServiceType.Win32OwnProcess, testServiceController.ServiceType); + Assert.True(testServiceController.CanPauseAndContinue); + Assert.True(testServiceController.CanStop); + Assert.True(testServiceController.CanShutdown); + } + + //[Fact] + // To cleanup lingering Test Services uncomment the Fact attribute and run the following command + // msbuild /t:rebuildandtest /p:XunitMethodName=System.ServiceProcess.Tests.ServiceBaseTests.Cleanup + // Remember to comment out the Fact again before running tests otherwise it will cleanup tests running in parallel + // and casue them to fail. + public void Cleanup() + { + string currentService = ""; + foreach (ServiceController controller in ServiceController.GetServices()) + { + try + { + currentService = controller.DisplayName; + if (controller.DisplayName.StartsWith("Test Service")) + { + Console.WriteLine("Trying to clean-up " + currentService); + TestServiceInstaller deleteService = new TestServiceInstaller() + { + ServiceName = controller.ServiceName + }; + deleteService.RemoveService(); + Console.WriteLine("Cleaned up " + currentService); + } + } + catch (Exception ex) + { + Console.WriteLine("Failed " + ex.Message); + } + } + } + + [ConditionalFact(nameof(RunningWithElevatedPrivileges))] + public void TestOnStartThenStop() + { + var controller = new ServiceController(_testService.TestServiceName); + AssertExpectedProperties(controller); + string expected = +@"OnStart args= +OnStop +"; + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped); + Assert.Equal(expected, _testService.GetServiceOutput()); + } + + [ConditionalFact(nameof(RunningWithElevatedPrivileges))] + public void TestOnStartWithArgsThenStop() + { + var controller = new ServiceController(_testService.TestServiceName); + AssertExpectedProperties(controller); + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped); + + string expected = +@"OnStart args=a,b,c +OnStop +"; + controller.Start(new string[] { "a", "b", "c" }); + controller.WaitForStatus(ServiceControllerStatus.Running); + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped); + Assert.Equal(expected, _testService.GetServiceOutput()); + } + + [ConditionalFact(nameof(RunningWithElevatedPrivileges))] + public void TestOnPauseThenStop() + { + var controller = new ServiceController(_testService.TestServiceName); + AssertExpectedProperties(controller); + string expected = +@"OnStart args= +OnPause +OnStop +"; + controller.Pause(); + controller.WaitForStatus(ServiceControllerStatus.Paused); + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped); + Assert.Equal(expected, _testService.GetServiceOutput()); + } + + [ConditionalFact(nameof(RunningWithElevatedPrivileges))] + public void TestOnPauseAndContinueThenStop() + { + var controller = new ServiceController(_testService.TestServiceName); + AssertExpectedProperties(controller); + string expected = +@"OnStart args= +OnPause +OnContinue +OnStop +"; + controller.Pause(); + controller.WaitForStatus(ServiceControllerStatus.Paused); + controller.Continue(); + controller.WaitForStatus(ServiceControllerStatus.Running); + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped); + Assert.Equal(expected, _testService.GetServiceOutput()); + } + + [ConditionalFact(nameof(RunningWithElevatedPrivileges))] + public void TestOnExecuteCustomCommand() + { + var controller = new ServiceController(_testService.TestServiceName); + AssertExpectedProperties(controller); + string expected = +@"OnStart args= +OnCustomCommand command=128 +OnStop +"; + controller.ExecuteCommand(128); + controller.WaitForStatus(ServiceControllerStatus.Running); + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped); + Assert.Equal(expected, _testService.GetServiceOutput()); + } + + [ConditionalFact(nameof(RunningWithElevatedPrivileges))] + public void TestOnContinueBeforePause() + { + var controller = new ServiceController(_testService.TestServiceName); + AssertExpectedProperties(controller); + string expected = +@"OnStart args= +OnStop +"; + controller.Continue(); + controller.WaitForStatus(ServiceControllerStatus.Running); + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped); + Assert.Equal(expected, _testService.GetServiceOutput()); + } + + public void Dispose() + { + _testService.DeleteTestServices(); + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/ServiceControllerTests.cs b/src/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs index 405f20c447..dfd3e5fc34 100644 --- a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/ServiceControllerTests.cs +++ b/src/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs @@ -10,82 +10,19 @@ using Xunit; namespace System.ServiceProcess.Tests { - internal sealed class ServiceProvider - { - public readonly string TestMachineName; - public readonly TimeSpan ControlTimeout; - public readonly string TestServiceName; - public readonly string TestServiceDisplayName; - public readonly string DependentTestServiceNamePrefix; - public readonly string DependentTestServiceDisplayNamePrefix; - public readonly string TestServiceRegistryKey; - - public ServiceProvider() - { - TestMachineName = "."; - ControlTimeout = TimeSpan.FromSeconds(120); - TestServiceName = Guid.NewGuid().ToString(); - TestServiceDisplayName = "Test Service " + TestServiceName; - DependentTestServiceNamePrefix = TestServiceName + ".Dependent"; - DependentTestServiceDisplayNamePrefix = TestServiceDisplayName + ".Dependent"; - TestServiceRegistryKey = @"HKEY_USERS\.DEFAULT\dotnetTests\ServiceController\" + TestServiceName; - - // Create the service - CreateTestServices(); - } - - private void CreateTestServices() - { - // Create the test service and its dependent services. Then, start the test service. - // All control tests assume that the test service is running when they are executed. - // So all tests should make sure to restart the service if they stop, pause, or shut - // it down. - RunServiceExecutable("create"); - } - - public void DeleteTestServices() - { - RunServiceExecutable("delete"); - RegistryKey users = Registry.Users; - if (users.OpenSubKey(".DEFAULT\\dotnetTests") != null) - users.DeleteSubKeyTree(".DEFAULT\\dotnetTests"); - } - - private void RunServiceExecutable(string action) - { - const string serviceExecutable = "System.ServiceProcess.ServiceController.TestNativeService.exe"; - var process = new Process(); - process.StartInfo.FileName = serviceExecutable; - process.StartInfo.Arguments = string.Format("\"{0}\" \"{1}\" {2}", TestServiceName, TestServiceDisplayName, action); - process.Start(); - process.WaitForExit(); - - if (process.ExitCode != 0) - { - throw new Exception("error: " + serviceExecutable + " failed with exit code " + process.ExitCode.ToString()); - } - } - } - [OuterLoop(/* Modifies machine state */)] - [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "Appx doesn't allow to access ServiceController")] public class ServiceControllerTests : IDisposable { - private const int ExpectedDependentServiceCount = 3; - - private static readonly Lazy<bool> s_runningWithElevatedPrivileges = new Lazy<bool>( - () => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)); - - private readonly ServiceProvider _testService; + private readonly TestServiceProvider _testService; public ServiceControllerTests() { - _testService = new ServiceProvider(); + _testService = new TestServiceProvider(); } private static bool RunningWithElevatedPrivileges { - get { return s_runningWithElevatedPrivileges.Value; } + get { return TestServiceProvider.RunningWithElevatedPrivileges; } } private void AssertExpectedProperties(ServiceController testServiceController) @@ -135,7 +72,7 @@ namespace System.ServiceProcess.Tests Assert.True(controller.CanStop); Assert.True(controller.CanPauseAndContinue); - Assert.False(controller.CanShutdown); + Assert.True(controller.CanShutdown); } [ConditionalFact(nameof(RunningWithElevatedPrivileges))] @@ -154,10 +91,9 @@ namespace System.ServiceProcess.Tests controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout); Assert.Equal(ServiceControllerStatus.Running, controller.Status); - // The test service writes the arguments that it was started with to the _testService.TestServiceRegistryKey. - // Read this key to verify that the arguments were properly passed to the service. - string argsString = Registry.GetValue(_testService.TestServiceRegistryKey, "ServiceArguments", null) as string; - Assert.Equal(string.Join(",", args), argsString); + string argsOutput = _testService.GetServiceOutput().Trim(); + string argsInput = "OnStart args=" + string.Join(",", args); + Assert.Equal(argsInput, argsOutput); } [ConditionalFact(nameof(RunningWithElevatedPrivileges))] @@ -228,32 +164,15 @@ namespace System.ServiceProcess.Tests // The test service creates a number of dependent services, each of which is depended on // by all the services created after it. var controller = new ServiceController(_testService.TestServiceName); - Assert.Equal(ExpectedDependentServiceCount, controller.DependentServices.Length); - - for (int i = 0; i < controller.DependentServices.Length; i++) - { - var dependent = AssertHasDependent(controller, _testService.DependentTestServiceNamePrefix + i, _testService.DependentTestServiceDisplayNamePrefix + i); - Assert.Equal(ServiceType.Win32OwnProcess, dependent.ServiceType); - - // Assert that this dependent service is depended on by all the test services created after it - Assert.Equal(ExpectedDependentServiceCount - i - 1, dependent.DependentServices.Length); + Assert.Equal(0, controller.DependentServices.Length); + Assert.Equal(1, controller.ServicesDependedOn.Length); - for (int j = i + 1; j < ExpectedDependentServiceCount; j++) - { - AssertHasDependent(dependent, _testService.DependentTestServiceNamePrefix + j, _testService.DependentTestServiceDisplayNamePrefix + j); - } - - // Assert that the dependent service depends on the main test service - AssertDependsOn(dependent, _testService.TestServiceName, _testService.TestServiceDisplayName); - - // Assert that this dependent service depends on all the test services created before it - Assert.Equal(i + 1, dependent.ServicesDependedOn.Length); + var dependentController = new ServiceController(_testService.TestServiceName + ".Dependent"); + Assert.Equal(1, dependentController.DependentServices.Length); + Assert.Equal(0, dependentController.ServicesDependedOn.Length); - for (int j = i - 1; j >= 0; j--) - { - AssertDependsOn(dependent, _testService.DependentTestServiceNamePrefix + j, _testService.DependentTestServiceDisplayNamePrefix + j); - } - } + Assert.Equal(controller.ServicesDependedOn[0].ServiceName, dependentController.ServiceName); + Assert.Equal(dependentController.DependentServices[0].ServiceName, controller.ServiceName); } [ConditionalFact(nameof(RunningWithElevatedPrivileges))] diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/NativeTestService.cpp b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/NativeTestService.cpp deleted file mode 100644 index 02cd760faa..0000000000 --- a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/NativeTestService.cpp +++ /dev/null @@ -1,664 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#include <windows.h> -#include <stdio.h> -#include <tchar.h> -#include <wchar.h> -#include <string> - -#define DEPENDENT_SERVICES 3 - -// The path to this executable -TCHAR gModulePath[MAX_PATH]; - -// Log file handle -HANDLE ghLogFile; -std::wstring gLogFilePath; - -// Main Test Service State -SERVICE_STATUS gServiceStatus; -SERVICE_STATUS_HANDLE gServiceStatusHandle; -HANDLE ghServiceStopEvent; -LPTSTR gServiceName; -LPTSTR gServiceDisplayName; - -// Dependent Service State -std::wstring gDependentServiceNames[DEPENDENT_SERVICES]; -std::wstring gDependentServiceDisplayNames[DEPENDENT_SERVICES]; - -// Service Management Methods -VOID GenerateDependentServiceNames(); -BOOL CreateTestServices(); -SC_HANDLE CreateTestService(SC_HANDLE, LPCTSTR, LPCTSTR, LPCTSTR, int, LPCTSTR dependencies = NULL); -BOOL DeleteTestServices(); -BOOL DeleteTestService(SC_HANDLE, LPCTSTR); - -// Service Methods -VOID WINAPI ServiceMain(DWORD, LPTSTR*); -VOID WINAPI ServiceCtrlHandler(DWORD); -VOID ServiceReportStatus(DWORD, DWORD, DWORD); -VOID ServiceInit(DWORD, LPTSTR*); -BOOL InitModulePath(); -VOID CreateLogFile(); -DWORD DeleteLogFile(); -VOID LogMessage(LPCTSTR format, ...); - -int _tmain(int argc, _TCHAR* argv []) -{ - if (argc < 3 || argc > 4) - { - puts("usage: System.ServiceProcess.ServiceController.TestNativeService.exe <ServiceName> <DisplayName> [create|delete]"); - return 1; - } - - gServiceName = argv[1]; - gServiceDisplayName = argv[2]; - - if (argc == 3) - { - // When run with just a service name, just run as a service - SERVICE_TABLE_ENTRY DispatchTable [] = - { - { gServiceName, (LPSERVICE_MAIN_FUNCTION) ServiceMain }, - { NULL, NULL } - }; - - // This call returns when the service has stopped. - // The process should simply terminate when the call returns. - if (!StartServiceCtrlDispatcher(DispatchTable)) - { - LogMessage(L"error: StartServiceCtrlDispatcher failed (%d)\n", GetLastError()); - } - } - else if (argc == 4) - { - if (!InitModulePath()) - { - return -1; - } - - GenerateDependentServiceNames(); - - std::wstring action = argv[3]; - if (action == L"create") - { - if (!CreateTestServices()) - { - wprintf(L"error: Creating the test services failed\n"); - DeleteTestServices(); - return -1; - } - } - else if (action == L"delete") - { - if (!DeleteTestServices()) - { - wprintf(L"error: Deleting the test services failed\n"); - return -1; - } - } - else - { - wprintf(L"error: Invalid action '%s'\n", action.c_str()); - return -1; - } - } - - return 0; -} - -VOID GenerateDependentServiceNames() -{ - LPCTSTR nameSuffix = L".Dependent"; - - for (int i = 0; i < DEPENDENT_SERVICES; i++) - { - std::wstring& name = gDependentServiceNames[i]; - name = gServiceName; - name = name + nameSuffix; - name += '0' + i; - - std::wstring& displayName = gDependentServiceDisplayNames[i]; - displayName = gServiceDisplayName; - displayName += nameSuffix; - displayName += '0' + i; - } -} - -BOOL CreateTestServices() -{ - // Get a handle to the SCM database. - - SC_HANDLE hScManager = OpenSCManager( - NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (hScManager == NULL) - { - wprintf(L"error: OpenSCManager failed (%d)\n", GetLastError()); - return false; - } - - // Create the main test service - - std::wstring serviceCommand = gModulePath; - serviceCommand += L" \""; - serviceCommand += gServiceName; - serviceCommand += L"\" \""; - serviceCommand += gServiceDisplayName; - serviceCommand += L"\""; - - SC_HANDLE hService = CreateTestService( - hScManager, - gServiceName, - gServiceDisplayName, - serviceCommand.c_str(), - SERVICE_DEMAND_START - ); - - if (hService == NULL) - { - CloseServiceHandle(hScManager); - return false; - } - - // Create dependent services - - std::wstring dependencies = gServiceName; - dependencies += (TCHAR)0; - - for (int i = 0; i < DEPENDENT_SERVICES; i++) - { - SC_HANDLE hDependentService = CreateTestService( - hScManager, - gDependentServiceNames[i].c_str(), - gDependentServiceDisplayNames[i].c_str(), - serviceCommand.c_str(), - SERVICE_DISABLED, - dependencies.c_str()); - - if (hDependentService == NULL) - { - CloseServiceHandle(hScManager); - return false; - } - - // Make each dependent service depend on all of the services before it - // for the sake of testing ServiceController.DependentServices and - // ServiceController.ServicesDepended on. We do this by inserting the name - // of the last dependent service created into the next dependent service's - // dependency list before the double null. - - dependencies.insert(dependencies.end() - 1, (TCHAR)0); - dependencies.insert(dependencies.length() - 1, gDependentServiceNames[i]); - } - - // Attempt to start the main test service - - BOOL result = StartService(hService, 0, NULL); - if (!result) - { - int error = GetLastError(); - if (error == ERROR_SERVICE_ALREADY_RUNNING) - { - wprintf(L"warning: Service '%s' is already running\n", gServiceName); - result = true; - } - else - { - wprintf(L"error: StartService failed (%d)\n", error); - } - } - - CloseServiceHandle(hService); - CloseServiceHandle(hScManager); - return result; -} - -SC_HANDLE CreateTestService(SC_HANDLE hScManager, LPCTSTR name, LPCTSTR displayName, LPCTSTR command, int startType, LPCTSTR dependencies) -{ - SC_HANDLE hService = CreateService( - hScManager, // SCM database - name, // name of service - displayName, // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - startType, // start type - SERVICE_ERROR_NORMAL, // error control type - command, // path to service's binary + arguments - NULL, // no load ordering group - NULL, // no tag identifier - dependencies, // dependencies (optional) - NULL, // LocalSystem account - NULL); // no password - - if (!hService) - { - BOOL result = false; - int error = GetLastError(); - - switch (error) - { - case ERROR_SERVICE_EXISTS: - wprintf(L"warning: Service '%s' already exists.\n", name); - - hService = OpenService(hScManager, name, SERVICE_ALL_ACCESS); - if (hService == NULL) - { - wprintf(L"error: Failed to open service '%s' (%d)\n", name, GetLastError()); - return NULL; - } - break; - - case ERROR_SERVICE_MARKED_FOR_DELETE: - wprintf(L"error: Service '%s' exists and has been marked for deletion.\n", name); - return NULL; - - default: - wprintf(L"error: Failed to create service '%s' (%d)\n", name, error); - return NULL; - } - } - - return hService; -} - -BOOL DeleteTestServices() -{ - SC_HANDLE hScManager = OpenSCManager( - NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (hScManager == NULL) - { - wprintf(L"error: OpenSCManager failed (%d)\n", GetLastError()); - return false; - } - - // Delete dependent services - - for (int i = 0; i < DEPENDENT_SERVICES; i++) - { - LPCTSTR name = gDependentServiceNames[i].c_str(); - - SC_HANDLE hDependentService = OpenService( - hScManager, - name, - SERVICE_ALL_ACCESS); - - if (hDependentService == NULL) - { - wprintf(L"warning: Failed to open service '%s' (%d)\n", name, GetLastError()); - continue; - } - - DeleteTestService(hDependentService, name); - CloseServiceHandle(hDependentService); - } - - // Stop and delete the main test service - - SC_HANDLE hService = OpenService( - hScManager, // SCM database - gServiceName, // name of service - SERVICE_ALL_ACCESS); // desired access - - if (hService == NULL) - { - wprintf(L"error: Failed to open service '%s' (%d)\n", gServiceName, GetLastError()); - CloseServiceHandle(hScManager); - return false; - } - - SERVICE_CONTROL_STATUS_REASON_PARAMS reasonParams = - { - SERVICE_STOP_REASON_FLAG_PLANNED | SERVICE_STOP_REASON_MAJOR_NONE | SERVICE_STOP_REASON_MINOR_INSTALLATION, - L"Stopping service for delete", - { 0 } - }; - - if (!ControlServiceEx(hService, SERVICE_CONTROL_STOP, SERVICE_CONTROL_STATUS_REASON_INFO, &reasonParams)) - { - int error = GetLastError(); - if (error == ERROR_SERVICE_NOT_ACTIVE) - { - wprintf(L"warning: Service '%s' is already stopped\n", gServiceName); - } - else - { - wprintf(L"warning: Failed to stop service (%d). Will still delete, but recreating may fail if the service is still running.\n", error); - } - } - - BOOL result = DeleteTestService(hService, gServiceName); - - CloseServiceHandle(hService); - CloseServiceHandle(hScManager); - return result; -} - -BOOL DeleteTestService(SC_HANDLE hService, LPCTSTR name) -{ - BOOL result = DeleteService(hService); - - if (!result) - { - wprintf(L"error: Failed to delete service '%s' (%d)\n", name, GetLastError()); - } - - return result; -} - -// -// Purpose: -// Entry point for the service -// -// Parameters: -// dwArgc - Number of arguments in the lpszArgv array -// lpszArgv - 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. -// -// Return value: -// None. -// -VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv) -{ - // Register the handler function for the service - - gServiceStatusHandle = RegisterServiceCtrlHandler(gServiceName, ServiceCtrlHandler); - - if (!gServiceStatusHandle) - { - LogMessage(L"error: RegisterServiceCtrlHandler failed (%d)\n", GetLastError()); - return; - } - - // These SERVICE_STATUS members remain as set here - - gServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; - gServiceStatus.dwServiceSpecificExitCode = 0; - - // Report initial status to the SCM - - ServiceReportStatus(SERVICE_START_PENDING, NO_ERROR, 3000); - - // Perform service-specific initialization and work. - - ServiceInit(dwArgc, lpszArgv); -} - -// -// Purpose: -// The service code -// -// Parameters: -// dwArgc - Number of arguments in the lpszArgv array -// lpszArgv - 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. -// -// Return value: -// None -// -VOID ServiceInit(DWORD dwArgc, LPTSTR* lpszArgv) -{ - // Create an event. The control handler function, ServiceCtrlHandler, - // signals this event when it receives the stop control code. - - ghServiceStopEvent = CreateEvent( - NULL, // default security attributes - TRUE, // manual reset event - FALSE, // not signaled - NULL); // no name - - if (ghServiceStopEvent == NULL) - { - ServiceReportStatus(SERVICE_STOPPED, NO_ERROR, 0); - return; - } - - InitModulePath(); - CreateLogFile(); - - // Write the service arguments to the registry key: - // HKEY_USERS\.DEFAULT\dotnetTests\ServiceController\<ServiceName>\ServiceArguments - // to verify that they were correctly passed through. - - std::wstring keyPath = L".DEFAULT\\dotnetTests\\ServiceController\\"; - keyPath += gServiceName; - - HKEY hKey; - LONG result = RegCreateKeyEx( - HKEY_USERS, - keyPath.c_str(), - 0, - NULL, - REG_OPTION_VOLATILE, - KEY_ALL_ACCESS, - NULL, - &hKey, - NULL); - - if (result != ERROR_SUCCESS) - { - LogMessage(L"warning: failed to open or create registry key 'HKEY_USERS\\%s' (%d)\n", keyPath.c_str(), result); - } - else - { - // Join the arguments array, separating each argument with a comma - - std::wstring argsString; - DWORD i = 1; - - for (; i < dwArgc - 1; i++) - { - argsString += lpszArgv[i]; - argsString += L','; - } - - if (i < dwArgc) - { - argsString += lpszArgv[i]; - } - - // Write the result to the value "ServiceArguments" - - LPCTSTR valueName = L"ServiceArguments"; - result = RegSetValueEx( - hKey, - valueName, - 0, - REG_SZ, - (const BYTE*) argsString.c_str(), - (DWORD) ((argsString.length() + 1) * sizeof(wchar_t))); - - if (result != ERROR_SUCCESS) - { - LogMessage(L"warning: failed to set value '%s' = '%s' in registry key 'HKEY_USERS\\%s' (%d)\n", valueName, argsString.c_str(), keyPath.c_str(), result); - } - - RegCloseKey(hKey); - } - - // Report running status when initialization is complete. - - ServiceReportStatus(SERVICE_RUNNING, NO_ERROR, 0); - - while (1) - { - // Check whether to stop the service. - - // If the tests haven't finished within 90 seconds, just end the program anyways. - DWORD error = WaitForSingleObject(ghServiceStopEvent, 90000); - - // We're stopping, delete the log file - DWORD logError = DeleteLogFile(); - - // If WaitForSingleObject fails, use that code. - // Otherwise use the result of DeleteLogFile. - if (error == ERROR_SUCCESS) - { - error = logError; - } - - ServiceReportStatus(SERVICE_STOPPED, error, 0); - return; - } -} - -// -// Purpose: -// Sets the current service status and reports it to the SCM. -// -// Parameters: -// dwCurrentState - The current state (see SERVICE_STATUS) -// dwWin32ExitCode - The system error code -// dwWaitHint - Estimated time for pending operation, -// in milliseconds -// -// Return value: -// None -// -VOID ServiceReportStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) -{ - static DWORD dwCheckPoint = 1; - - // Fill in the SERVICE_STATUS structure. - - gServiceStatus.dwCurrentState = dwCurrentState; - gServiceStatus.dwWin32ExitCode = dwWin32ExitCode; - gServiceStatus.dwWaitHint = dwWaitHint; - - if (dwCurrentState == SERVICE_START_PENDING) - gServiceStatus.dwControlsAccepted = 0; - else gServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; - - if ((dwCurrentState == SERVICE_RUNNING) || - (dwCurrentState == SERVICE_STOPPED)) - gServiceStatus.dwCheckPoint = 0; - else gServiceStatus.dwCheckPoint = dwCheckPoint++; - - // Report the status of the service to the SCM. - - SetServiceStatus(gServiceStatusHandle, &gServiceStatus); -} - -// -// Purpose: -// Called by SCM whenever a control code is sent to the service -// using the ControlService function. -// -// Parameters: -// dwCtrl - control code -// -// Return value: -// None -// -VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl) -{ - // Handle the requested control code. - - switch (dwCtrl) - { - case SERVICE_CONTROL_STOP: - ServiceReportStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); - - // Signal the service to stop. - - SetEvent(ghServiceStopEvent); - ServiceReportStatus(gServiceStatus.dwCurrentState, NO_ERROR, 0); - break; - - case SERVICE_CONTROL_PAUSE: - ServiceReportStatus(SERVICE_PAUSED, NO_ERROR, 0); - break; - - case SERVICE_CONTROL_CONTINUE: - ServiceReportStatus(SERVICE_RUNNING, NO_ERROR, 0); - break; - - case SERVICE_CONTROL_INTERROGATE: - break; - - default: - break; - } -} - -BOOL InitModulePath() -{ - if (!GetModuleFileName(NULL, gModulePath, MAX_PATH)) - { - wprintf(L"error: Failed to get module file name (%d)\n", GetLastError()); - return FALSE; - } - - return TRUE; -} - -VOID CreateLogFile() -{ - gLogFilePath = gModulePath; - gLogFilePath += L'.'; - gLogFilePath += gServiceName; - gLogFilePath += L".txt"; - - ghLogFile = CreateFile( - gLogFilePath.c_str(), - GENERIC_WRITE, - FILE_SHARE_READ, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (ghLogFile == INVALID_HANDLE_VALUE) - { - wprintf(L"warning: Failed to create log file '%s'\n", gLogFilePath.c_str()); - } -} - -DWORD DeleteLogFile() -{ - CloseHandle(ghLogFile); - - if (!DeleteFile(gLogFilePath.c_str())) - { - DWORD error = GetLastError(); - return error; - } - - return NO_ERROR; -} - -VOID LogMessage(LPCTSTR format, ...) -{ - TCHAR buffer[256]; - va_list args; - va_start(args, format); - - int numChars = _vstprintf_s(buffer, 256, format, args); - - BOOL result = WriteFile( - ghLogFile, - buffer, - numChars * sizeof(TCHAR), - NULL, - NULL); - - if (!result) - { - wprintf(L"warning: Failed to write to the log file (%d): %s", GetLastError(), buffer); - } - - va_end(args); -} diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/System.ServiceProcess.ServiceController.TestNativeService.vcxproj b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/System.ServiceProcess.ServiceController.TestNativeService.vcxproj deleted file mode 100644 index c051214af8..0000000000 --- a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/System.ServiceProcess.ServiceController.TestNativeService.vcxproj +++ /dev/null @@ -1,66 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Release|x64"> - <Configuration>Release</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{CEB0775C-4273-4AC4-B50E-4492718051AE}</ProjectGuid> - <Keyword>Win32Proj</Keyword> - <Configuration>Release</Configuration> - <Platform>x64</Platform> - <RootNamespace>NativeTestService</RootNamespace> - <OutDir>$(OutputPath)</OutDir> - <!-- Defaulting to v120 but if we have higher tools it will be overridden in Microsoft.Cpp.Default.props --> - <DefaultPlatformToolset>v120</DefaultPlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - </Link> - </ItemDefinitionGroup> - <ItemGroup> - <ClCompile Include="NativeTestService.cpp" /> - <!-- Add target to the Copy list so referencing projects will get it copied to their output as well --> - <None Include="$(TargetPath)"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </None> - </ItemGroup> - <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project>
\ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/Configurations.props b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Configurations.props index c398e42e89..5e52d60de3 100644 --- a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/Configurations.props +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Configurations.props @@ -2,7 +2,8 @@ <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <BuildConfigurations> - netstandard; + netcoreapp; + netfx; </BuildConfigurations> </PropertyGroup> </Project>
\ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs new file mode 100644 index 0000000000..e085b6a0c5 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace System.ServiceProcess.Tests +{ + public class Program + { + static int Main(string[] args) + { + if (args.Length == 1 || args.Length == 2) + { + TestService testService = new TestService(args[0]); + ServiceBase.Run(testService); + return 0; + } + else if (args.Length == 3) + { + TestServiceInstaller testServiceInstaller = new TestServiceInstaller(); + + testServiceInstaller.ServiceName = args[0]; + testServiceInstaller.DisplayName = args[1]; + + if (args[2] == "create") + { + testServiceInstaller.Install(); + return 0; + } + else if (args[2] == "delete") + { + testServiceInstaller.RemoveService(); + return 0; + } + else + { + Console.WriteLine("EROOR: Invalid Service verb. Only suppot create or delete."); + return 2; + } + } + + Console.WriteLine($"usage: <ServiceName> <DisplayName> [create|delete]"); + return 1; + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.csproj b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.csproj new file mode 100644 index 0000000000..f9a346b6d3 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.csproj @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <PropertyGroup> + <ProjectGuid>{E62B874D-1A0D-41BC-8CFC-9E09D0860A77}</ProjectGuid> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <OutputType>exe</OutputType> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Debug|AnyCPU'" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Release|AnyCPU'" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netfx-Debug|AnyCPU'" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netfx-Release|AnyCPU'" /> + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="TestServiceInstaller.cs" /> + <Compile Include="TestService.cs"> + <SubType>Component</SubType> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs"> + <Link>Common\Interop\Windows\Interop.Libraries.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.ServiceProcessOptions.cs"> + <Link>Common\Interop\Windows\Interop.ServiceProcessOptions.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.CloseServiceHandle.cs"> + <Link>Common\Interop\Windows\Interop.CloseServiceHandle.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.OpenSCManager.cs"> + <Link>Common\Interop\Windows\Interop.OpenSCManager.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.OpenService.cs"> + <Link>Common\Interop\Windows\Interop.OpenService.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.CreateService.cs"> + <Link>Common\Interop\Windows\Interop.CreateService.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.SERVICE_DESCRIPTION.cs"> + <Link>Common\Interop\Windows\Interop.SERVICE_DESCRIPTION.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.ChangeServiceConfig2.cs"> + <Link>Common\Interop\Windows\Interop.ChangeServiceConfig2.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.SERVICE_DELAYED_AUTOSTART_INFO.cs"> + <Link>Common\Interop\Windows\Interop.SERVICE_DELAYED_AUTOSTART_INFO.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\advapi32\Interop.DeleteService.cs"> + <Link>Common\Interop\Windows\Interop.DeleteService.cs</Link> + </Compile> + </ItemGroup> + <ItemGroup> + <Content Include="System.ServiceProcess.ServiceController.TestService.runtimeconfig.json"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> + </ItemGroup> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project>
\ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.runtimeconfig.json b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.runtimeconfig.json new file mode 100644 index 0000000000..573682438b --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.runtimeconfig.json @@ -0,0 +1,8 @@ +{ + "runtimeOptions": { + "framework": { + "name": "Microsoft.NETCore.App", + "version": "9.9.9" + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs new file mode 100644 index 0000000000..24a8467e3b --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Diagnostics; +using System; +using System.Collections; +using System.IO; +using System.Threading; +using System.Text; +using System.Runtime.InteropServices; +using System.Globalization; + +namespace System.ServiceProcess.Tests +{ + public class TestService : ServiceBase + { + public TestService(string serviceName) + { + this.ServiceName = serviceName; + + // Enable all the events + this.CanPauseAndContinue = true; + this.CanStop = true; + this.CanShutdown = true; + + // We cannot easily test these so disable the events + this.CanHandleSessionChangeEvent = false; + this.CanHandlePowerEvent = false; + } + + protected override void OnContinue() + { + WriteLog(nameof(OnContinue)); + base.OnContinue(); + } + + protected override void OnCustomCommand(int command) + { + WriteLog(nameof(OnCustomCommand) + " command=" + command); + base.OnCustomCommand(command); + } + + protected override void OnPause() + { + WriteLog(nameof(OnPause)); + base.OnPause(); + } + + protected override void OnSessionChange(SessionChangeDescription changeDescription) + { + WriteLog(nameof(OnSessionChange) + " change=" + changeDescription.ToString()); + base.OnSessionChange(changeDescription); + } + + protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) + { + WriteLog(nameof(OnPowerEvent) + " status=" + powerStatus.ToString()); + return base.OnPowerEvent(powerStatus); + } + + protected override void OnShutdown() + { + WriteLog(nameof(OnShutdown)); + base.OnShutdown(); + } + + protected override void OnStart(string[] args) + { + WriteLog(nameof(OnStart) + " args=" + string.Join(",", args)); + base.OnStart(args); + } + + protected override void OnStop() + { + WriteLog(nameof(OnStop)); + base.OnStop(); + + if (_log != null) + { + _log.Dispose(); + _log = null; + } + } + + private StreamWriter _log; + + private void WriteLog(string msg) + { + if (_log == null) + { + string path = System.Reflection.Assembly.GetEntryAssembly().Location + "." + ServiceName + ".log"; + _log = new StreamWriter(path); + _log.AutoFlush = true; +; } + + _log.WriteLine(msg); + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs new file mode 100644 index 0000000000..9bd9c12807 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; +using System.Text; +using System.Runtime.InteropServices; + +namespace System.ServiceProcess.Tests +{ + public class TestServiceInstaller + { + public const string LocalServiceName = "NT AUTHORITY\\LocalService"; + + public TestServiceInstaller() + { + } + + public string DisplayName { get; set; } = string.Empty; + + public string Description { get; set; } = string.Empty; + + public string[] ServicesDependedOn { get; set; } = Array.Empty<string>(); + + public string ServiceName { get; set; } = string.Empty; + + public ServiceStartMode StartType { get; set; } = ServiceStartMode.Manual; + + public string Username { get; set; } + + public string Password { get; set; } + + public string ServiceCommandLine { get; set; } + + public unsafe void Install() + { + string username = Username; + string password = Password; + + if (string.IsNullOrEmpty(username)) + { + username = LocalServiceName; + } + + if (ServiceCommandLine == null) + { + string processName = Process.GetCurrentProcess().MainModule.FileName; + string entryPointName = System.Reflection.Assembly.GetEntryAssembly().Location; + string arguments = ServiceName; + + // if process and entry point aren't the same then we are running hosted so pass + // in the entrypoint as the first argument + if (!string.Equals(processName, entryPointName, StringComparison.OrdinalIgnoreCase)) + { + arguments = $"\"{entryPointName}\" {arguments}"; + } + + ServiceCommandLine = $"\"{processName}\" {arguments}"; + } + + //Build servicesDependedOn string + string servicesDependedOn = null; + if (ServicesDependedOn.Length > 0) + { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < ServicesDependedOn.Length; ++i) + { + //The servicesDependedOn need to be separated by a null + buff.Append(ServicesDependedOn[i]); + buff.Append('\0'); + } + // an extra null at the end indicates end of list. + buff.Append('\0'); + + servicesDependedOn = buff.ToString(); + } + + // Open the service manager + IntPtr serviceManagerHandle = Interop.Advapi32.OpenSCManager(null, null, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_ALL); + IntPtr serviceHandle = IntPtr.Zero; + if (serviceManagerHandle == IntPtr.Zero) + throw new InvalidOperationException("Cannot open Service Control Manager"); + + try + { + // Install the service + serviceHandle = Interop.Advapi32.CreateService(serviceManagerHandle, ServiceName, + DisplayName, Interop.Advapi32.ServiceAccessOptions.ACCESS_TYPE_ALL, Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_WIN32_OWN_PROCESS, + (int)StartType, Interop.Advapi32.ServiceStartErrorModes.ERROR_CONTROL_NORMAL, + ServiceCommandLine, null, IntPtr.Zero, servicesDependedOn, username, password); + + if (serviceHandle == IntPtr.Zero) + throw new Win32Exception(); + + // A local variable in an unsafe method is already fixed -- so we don't need a "fixed { }" blocks to protect + // across the p/invoke calls below. + + if (Description.Length != 0) + { + Interop.Advapi32.SERVICE_DESCRIPTION serviceDesc = new Interop.Advapi32.SERVICE_DESCRIPTION(); + serviceDesc.description = Marshal.StringToHGlobalUni(Description); + bool success = Interop.Advapi32.ChangeServiceConfig2(serviceHandle, Interop.Advapi32.ServiceConfigOptions.SERVICE_CONFIG_DESCRIPTION, ref serviceDesc); + Marshal.FreeHGlobal(serviceDesc.description); + if (!success) + throw new Win32Exception(); + } + + // Start the service after creating it + using (ServiceController svc = new ServiceController(ServiceName)) + { + if (svc.Status != ServiceControllerStatus.Running) + { + svc.Start(); + svc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); + } + } + } + finally + { + if (serviceHandle != IntPtr.Zero) + Interop.Advapi32.CloseServiceHandle(serviceHandle); + + Interop.Advapi32.CloseServiceHandle(serviceManagerHandle); + } + } + + public void RemoveService() + { + // Stop the service + using (ServiceController svc = new ServiceController(ServiceName)) + { + if (svc.Status != ServiceControllerStatus.Stopped) + { + svc.Stop(); + svc.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); + } + } + + IntPtr serviceManagerHandle = Interop.Advapi32.OpenSCManager(null, null, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_ALL); + if (serviceManagerHandle == IntPtr.Zero) + throw new Win32Exception(); + + IntPtr serviceHandle = IntPtr.Zero; + try + { + serviceHandle = Interop.Advapi32.OpenService(serviceManagerHandle, + ServiceName, Interop.Advapi32.ServiceOptions.STANDARD_RIGHTS_DELETE); + + if (serviceHandle == IntPtr.Zero) + throw new Win32Exception(); + + if (!Interop.Advapi32.DeleteService(serviceHandle)) + throw new Win32Exception(); + } + finally + { + if (serviceHandle != IntPtr.Zero) + Interop.Advapi32.CloseServiceHandle(serviceHandle); + + Interop.Advapi32.CloseServiceHandle(serviceManagerHandle); + } + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/System.ServiceProcess.ServiceController.Tests.csproj b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj index 0e9d264310..55a1762218 100644 --- a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/System.ServiceProcess.ServiceController.Tests.csproj +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj @@ -4,24 +4,21 @@ <PropertyGroup> <ProjectGuid>{F7D9984B-02EB-4573-84EF-00FFFBFB872C}</ProjectGuid> </PropertyGroup> - <!-- Default configurations to help VS understand the configurations --> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Debug|AnyCPU'" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Release|AnyCPU'" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Windows_NT-Debug|AnyCPU'" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Windows_NT-Release|AnyCPU'" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netfx-Windows_NT-Debug|AnyCPU'" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netfx-Windows_NT-Release|AnyCPU'" /> <ItemGroup> + <Compile Include="ServiceBaseTests.cs" /> + <Compile Include="TestServiceProvider.cs" /> <Compile Include="SafeServiceControllerTests.cs" /> <Compile Include="ServiceControllerTests.cs" /> <Compile Include="$(CommonTestPath)\System\PlatformDetection.cs"> <Link>CommonTest\System\PlatformDetection.cs</Link> </Compile> </ItemGroup> - <ItemGroup Condition="'$(TargetsWindows)'=='true' AND '$(Platform)'!='arm64'"> - <ProjectReference Include="..\System.ServiceProcess.ServiceController.TestNativeService\System.ServiceProcess.ServiceController.TestNativeService.vcxproj"> - <Project>{ceb0775c-4273-4ac4-b50e-4492718051ae}</Project> - <Name>System.ServiceProcess.ServiceController.TestNativeService</Name> - <UndefineProperties>OSGroup;TargetGroup;Configuration</UndefineProperties> - <SetConfiguration>Configuration=Release</SetConfiguration> - <SetPlatform Condition="'$(Platform)'=='AnyCPU'">Platform=x64</SetPlatform> - </ProjectReference> + <ItemGroup> + <ProjectReference Include=".\System.ServiceProcess.ServiceController.TestService\System.ServiceProcess.ServiceController.TestService.csproj" /> </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> </Project>
\ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs b/src/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs new file mode 100644 index 0000000000..30b499a324 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs @@ -0,0 +1,124 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using System; +using System.Diagnostics; +using System.Security.Principal; +using Xunit; +using System.IO; +using System.Threading; + +namespace System.ServiceProcess.Tests +{ + internal sealed class TestServiceProvider + { + private static readonly Lazy<bool> s_runningWithElevatedPrivileges = new Lazy<bool>( + () => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)); + + public static bool RunningWithElevatedPrivileges + { + get { return s_runningWithElevatedPrivileges.Value; } + } + + public readonly string TestServiceAssembly = typeof(TestService).Assembly.Location; + public readonly string TestMachineName; + public readonly TimeSpan ControlTimeout; + public readonly string TestServiceName; + public readonly string TestServiceDisplayName; + + private readonly TestServiceProvider _dependentServices; + public TestServiceProvider() + { + TestMachineName = "."; + ControlTimeout = TimeSpan.FromSeconds(120); + TestServiceName = Guid.NewGuid().ToString(); + TestServiceDisplayName = "Test Service " + TestServiceName; + + _dependentServices = new TestServiceProvider(TestServiceName + ".Dependent"); + + // Create the service + CreateTestServices(); + } + + public TestServiceProvider(string serviceName) + { + TestMachineName = "."; + ControlTimeout = TimeSpan.FromSeconds(120); + TestServiceName = serviceName; + TestServiceDisplayName = "Test Service " + TestServiceName; + + // Create the service + CreateTestServices(); + } + + private void CreateTestServices() + { + TestServiceInstaller testServiceInstaller = new TestServiceInstaller(); + + testServiceInstaller.ServiceName = TestServiceName; + testServiceInstaller.DisplayName = TestServiceDisplayName; + + if (_dependentServices != null) + { + testServiceInstaller.ServicesDependedOn = new string[] { _dependentServices.TestServiceName }; + } + + var comparer = PlatformDetection.IsFullFramework ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; // Full framework upper cases the name + string processName = Process.GetCurrentProcess().MainModule.FileName; + string entryPointName = typeof(TestService).Assembly.Location; + string arguments = TestServiceName; + + // if process and entry point aren't the same then we are running hosted so pass + // in the entrypoint as the first argument + if (!PlatformDetection.IsFullFramework) + { + arguments = $"\"{entryPointName}\" {arguments}"; + } + else + { + processName = entryPointName; + } + + testServiceInstaller.ServiceCommandLine = $"\"{processName}\" {arguments}"; + + testServiceInstaller.Install(); + } + + public void DeleteTestServices() + { + try + { + TestServiceInstaller testServiceInstaller = new TestServiceInstaller(); + testServiceInstaller.ServiceName = TestServiceName; + testServiceInstaller.RemoveService(); + + if (File.Exists(LogPath)) + { + File.Delete(LogPath); + } + } + finally + { + // Lets be sure to try and clean up dependenct services even if something goes + // wrong with the full removal of the other service. + if (_dependentServices != null) + { + _dependentServices.DeleteTestServices(); + } + } + } + + private string LogPath => typeof(TestService).Assembly.Location + "." + TestServiceName + ".log"; + + public string GetServiceOutput() + { + // Need to open with FileShare.ReadWrite because we expect the service still has it open for write + using (StreamReader reader = new StreamReader(File.Open(LogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) + { + return reader.ReadToEnd(); + } + } + } +} |