diff --git a/src/libraries/Common/src/Interop/Windows/advapi32/Interop.ChangeServiceConfig2.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.ChangeServiceConfig2.cs new file mode 100644 index 00000000000000..9b92147954c677 --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.CreateService.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.CreateService.cs new file mode 100644 index 00000000000000..9bb9a73678715e --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.DeleteService.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.DeleteService.cs new file mode 100644 index 00000000000000..4bd1b9801b0006 --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandler.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandler.cs new file mode 100644 index 00000000000000..cbf6a99eb9bd3b --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandlerEx.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.RegisterServiceCtrlHandlerEx.cs new file mode 100644 index 00000000000000..f2b494abf32af1 --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DELAYED_AUTOSTART_INFO.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DELAYED_AUTOSTART_INFO.cs new file mode 100644 index 00000000000000..249230d7ddcb71 --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DESCRIPTION.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.SERVICE_DESCRIPTION.cs new file mode 100644 index 00000000000000..53f1bfd4b8caac --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.SERVICE_TABLE_ENTRY.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.SERVICE_TABLE_ENTRY.cs new file mode 100644 index 00000000000000..59ab422f8794f4 --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.ServiceControlDelegates.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.ServiceControlDelegates.cs new file mode 100644 index 00000000000000..8c4034b9b45ca6 --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.ServiceProcessOptions.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.ServiceProcessOptions.cs index 20eaa5e77fc7a7..5e4a93b38f7164 100644 --- a/src/libraries/Common/src/Interop/Windows/advapi32/Interop.ServiceProcessOptions.cs +++ b/src/libraries/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 AcceptOptions 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 ServiceOptions 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 ServiceTypeOptions 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 ServiceControlStatus 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/libraries/Common/src/Interop/Windows/advapi32/Interop.SetServiceStatus.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.SetServiceStatus.cs new file mode 100644 index 00000000000000..e20f22006f9ae0 --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.StartServiceCtrlDispatcher.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.StartServiceCtrlDispatcher.cs new file mode 100644 index 00000000000000..a6773ae5efd262 --- /dev/null +++ b/src/libraries/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/libraries/Common/src/Interop/Windows/advapi32/Interop.WTSSESSION_NOTIFICATION.cs b/src/libraries/Common/src/Interop/Windows/advapi32/Interop.WTSSESSION_NOTIFICATION.cs new file mode 100644 index 00000000000000..86503af47ab623 --- /dev/null +++ b/src/libraries/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/libraries/System.ServiceProcess.ServiceController/System.ServiceProcess.ServiceController.sln b/src/libraries/System.ServiceProcess.ServiceController/System.ServiceProcess.ServiceController.sln index ac2c5a484e8f25..a4b8be352c4968 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/System.ServiceProcess.ServiceController.sln +++ b/src/libraries/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/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.cs b/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.cs index 5bc0811eac44b5..e10f43db51b6d2 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.cs +++ b/src/libraries/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 @@ public enum ServiceType 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/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj b/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj index 5d6d29f3cd5d6b..dc2cae1910db31 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj +++ b/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj @@ -14,7 +14,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/Configurations.props b/src/libraries/System.ServiceProcess.ServiceController/src/Configurations.props index 4e5de270866ab6..ae3efb2d39cbc8 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/Configurations.props +++ b/src/libraries/System.ServiceProcess.ServiceController/src/Configurations.props @@ -2,7 +2,7 @@ - netstandard-Windows_NT; + netcoreapp-Windows_NT; netstandard; netfx-Windows_NT; diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/Resources/Strings.resx b/src/libraries/System.ServiceProcess.ServiceController/src/Resources/Strings.resx index 7111ac4072f74f..270edcd7711fa4 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/Resources/Strings.resx +++ b/src/libraries/System.ServiceProcess.ServiceController/src/Resources/Strings.resx @@ -100,4 +100,97 @@ ServiceController enables manipulating and accessing Windows services and it is not applicable for other operating systems. + + Cannot change CanStop, CanPauseAndContinue, CanShutdown, CanHandlePowerEvent, or CanHandleSessionChangeEvent property values after the service has been started. + + + Cannot change service name when the service is running. + + + Service name {0} contains invalid characters, is empty, or is too long (max length = {1}). + + + Service has not been supplied. At least one object derived from ServiceBase is required in order to run. + + + 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. + + + UpdatePendingStatus can only be called during the handling of Start, Stop, Pause and Continue commands. + + + Service started successfully. + + + Service stopped successfully. + + + Service paused successfully. + + + Service continued successfully. + + + Service was installed successfully. + + + Service was uninstalled successfully. + + + Service command was processed successfully. + + + Service cannot be started. {0} + + + Failed to stop service. {0} + + + Failed to pause service. {0} + + + Failed to continue service. {0} + + + Failed to process session change. {0} + + + Failed to install service. Service may have been installed already. + + + Failed to uninstall service. Service may be running. + + + Failed to process service command. {0} + + + Windows Error number: {0}. + + + Service has been successfully shut down. + + + Failed to shut down service. The error that occurred was: {0}. + + + PowerEvent handled successfully by the service. + + + Failed in handling the PowerEvent. The error that occurred was: {0}. + + + Service {0} has been successfully installed. + + + Attempt to stop service {0}. + + + Service {0} is being removed from the system... + + + Service {0} was successfully removed from the system. + + + Cannot control {0} service on computer '{1}'. + \ No newline at end of file diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj b/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj index 1545cef32a5f42..13348c1079f0db 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj @@ -8,7 +8,7 @@ {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05} true None - SR.PlatformNotSupported_ServiceController + SR.PlatformNotSupported_ServiceController false @@ -18,7 +18,7 @@ - + Common\Interop\Windows\Interop.Libraries.cs @@ -67,25 +67,54 @@ Common\Interop\Windows\Interop.SERVICE_STATUS.cs + + Common\Interop\Windows\Interop.SERVICE_TABLE_ENTRY.cs + + + Common\Interop\Windows\Interop.ServiceControlDelegates.cs + + + Common\Interop\Windows\Interop.SetServiceStatus.cs + + + Common\Interop\Windows\Interop.WTSSESSION_NOTIFICATION.cs + + + Common\Interop\Windows\Interop.RegisterServiceCtrlHandler.cs + + + Common\Interop\Windows\Interop.RegisterServiceCtrlHandlerEx.cs + + + Common\Interop\Windows\Interop.StartServiceCtrlDispatcher.cs + + + + + - + + + + + \ No newline at end of file diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/PowerBroadcastStatus.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/PowerBroadcastStatus.cs new file mode 100644 index 00000000000000..5d4b09b9a24aa4 --- /dev/null +++ b/src/libraries/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/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs new file mode 100644 index 00000000000000..d4be605cd88813 --- /dev/null +++ b/src/libraries/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 +{ + /// + /// Provides a base class for a service that will exist as part of a service application. + /// must be derived when creating a new service class. + /// + 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; + + /// + /// + /// Indicates the maximum size for a service name. + /// + /// + public const int MaxNameLength = 80; + + /// + /// Creates a new instance of the class. + /// + public ServiceBase() + { + _acceptedCommands = AcceptOptions.ACCEPT_STOP; + ServiceName = ""; + } + + /// + /// 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. + /// + 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); + } + } + + /// + /// The termination code for the service. Set this to a non-zero value before + /// stopping to indicate an error to the Service Control Manager. + /// + public int ExitCode + { + get + { + return _status.win32ExitCode; + } + set + { + _status.win32ExitCode = value; + } + } + + /// + /// Indicates whether the service can be handle notifications on + /// computer power status changes. + /// + [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; + } + } + } + + /// + /// Indicates whether the service can handle Terminal Server session change events. + /// + [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; + } + } + } + + /// + /// Indicates whether the service can be paused + /// and resumed. + /// + [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; + } + } + } + + /// + /// Indicates whether the service should be notified when + /// the system is shutting down. + /// + [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; + } + } + } + + /// + /// Indicates whether the service can be + /// stopped once it has started. + /// + [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; + } + } + + /// + /// Indicates the short name used to identify the service to the system. + /// + 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; + } + + /// + /// Disposes of the resources (other than memory ) used by + /// the . + /// + 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); + } + + /// + /// 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. + /// + protected virtual void OnContinue() + { + } + + /// + /// 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. + /// + protected virtual void OnPause() + { + } + + /// + /// + /// When implemented in a derived class, executes when the computer's + /// power status has changed. + /// + /// + protected virtual bool OnPowerEvent(PowerBroadcastStatus powerStatus) + { + return true; + } + + /// + /// When implemented in a derived class, + /// executes when a Terminal Server session change event is received. + /// + protected virtual void OnSessionChange(SessionChangeDescription changeDescription) + { + } + + /// + /// When implemented in a derived class, + /// executes when the system is shutting down. + /// Specifies what should + /// happen just prior + /// to the system shutting down. + /// + protected virtual void OnShutdown() + { + } + + /// + /// 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. + /// + /// 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? + /// + /// + protected virtual void OnStart(string[] args) + { + } + + /// + /// 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. + /// + 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; + } + } + + /// + /// When implemented in a derived class, + /// executes when a custom command is passed to + /// the service. Specifies the actions to take when + /// a command with the specified parameter value occurs. + /// + /// 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? + /// + /// + protected virtual void OnCustomCommand(int command) + { + } + + /// + /// Provides the main entry point for an executable that + /// contains multiple associated services. Loads the specified services into memory so they can be + /// started. + /// + 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); + } + } + } + + /// + /// Provides the main + /// entry point for an executable that contains a single + /// service. Loads the service into memory so it can be + /// started. + /// + 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; + } + + /// + /// 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. + /// + /// + 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(); + } + + /// + /// ServiceMain callback is called by NT . + /// It is expected that we register the command handler, + /// and start the service at this point. + /// + /// + [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/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs index dace4866a0e702..0dabc4e9cc641e 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs +++ b/src/libraries/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 @@ public class ServiceController : IDisposable 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 @@ public string DisplayName 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 @@ public string MachineName { 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 @@ public string ServiceName 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 @@ public unsafe ServiceController[] ServicesDependedOn 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(); while (*(dependencyChar + length) != '\0') @@ -391,17 +448,13 @@ private static bool CheckMachineName(string value) 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 @@ public unsafe void Continue() } } + 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/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs new file mode 100644 index 00000000000000..8889f1803178fe --- /dev/null +++ b/src/libraries/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/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs new file mode 100644 index 00000000000000..b3c3ee10264133 --- /dev/null +++ b/src/libraries/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 + { + /// + /// A session was connected to the console session. + /// + ConsoleConnect = Interop.Advapi32.SessionStateChange.WTS_CONSOLE_CONNECT, + /// + /// A session was disconnected from the console session. + /// + ConsoleDisconnect = Interop.Advapi32.SessionStateChange.WTS_CONSOLE_DISCONNECT, + /// + /// A session was connected to the remote session. + /// + RemoteConnect = Interop.Advapi32.SessionStateChange.WTS_REMOTE_CONNECT, + /// + /// A session was disconnected from the remote session. + /// + RemoteDisconnect = Interop.Advapi32.SessionStateChange.WTS_REMOTE_DISCONNECT, + /// + /// A user has logged on to the session. + /// + SessionLogon = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOGON, + /// + /// A user has logged off the session. + /// + SessionLogoff = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOGOFF, + /// + /// A session has been locked. + /// + SessionLock = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOCK, + /// + /// A session has been unlocked. + /// + SessionUnlock = Interop.Advapi32.SessionStateChange.WTS_SESSION_UNLOCK, + /// + /// A session has changed its remote controlled status. + /// + SessionRemoteControl = Interop.Advapi32.SessionStateChange.WTS_SESSION_REMOTE_CONTROL + } +} + diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs index 600d37744c24f0..2b71e989e5cf25 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs @@ -7,7 +7,7 @@ namespace System.ServiceProcess { - public class TimeoutException : Exception + public class TimeoutException : SystemException { private const int ServiceControllerTimeout = unchecked((int)0x80131906); diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/Configurations.props b/src/libraries/System.ServiceProcess.ServiceController/tests/Configurations.props similarity index 82% rename from src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/Configurations.props rename to src/libraries/System.ServiceProcess.ServiceController/tests/Configurations.props index 249c8c18b46b6b..552f5e92bccd51 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/Configurations.props +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/Configurations.props @@ -2,7 +2,8 @@ - netstandard-Windows_NT; + netcoreapp-Windows_NT; + netfx-Windows_NT; \ No newline at end of file diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/SafeServiceControllerTests.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/SafeServiceControllerTests.cs similarity index 97% rename from src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/SafeServiceControllerTests.cs rename to src/libraries/System.ServiceProcess.ServiceController/tests/SafeServiceControllerTests.cs index 1cb0515ef868d4..c9ffab58bf2fe3 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/SafeServiceControllerTests.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/SafeServiceControllerTests.cs @@ -6,7 +6,6 @@ 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 @@ public static void GetServices() 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/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs new file mode 100644 index 00000000000000..db05035a82bf37 --- /dev/null +++ b/src/libraries/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; + +/// +/// NOTE: All tests checking the output file should always call Stop before checking because Stop will flush the file to disk. +/// +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/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/ServiceControllerTests.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs similarity index 63% rename from src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/ServiceControllerTests.cs rename to src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs index 405f20c447d3c1..dfd3e5fc34a0f9 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/ServiceControllerTests.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs @@ -10,82 +10,19 @@ 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 s_runningWithElevatedPrivileges = new Lazy( - () => 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 @@ public void ControlCapabilities() Assert.True(controller.CanStop); Assert.True(controller.CanPauseAndContinue); - Assert.False(controller.CanShutdown); + Assert.True(controller.CanShutdown); } [ConditionalFact(nameof(RunningWithElevatedPrivileges))] @@ -154,10 +91,9 @@ public void StartWithArguments() 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 @@ public void Dependencies() // 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/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/NativeTestService.cpp b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/NativeTestService.cpp deleted file mode 100644 index 02cd760faaeca9..00000000000000 --- a/src/libraries/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 -#include -#include -#include -#include - -#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 [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\\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/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/System.ServiceProcess.ServiceController.TestNativeService.vcxproj b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/System.ServiceProcess.ServiceController.TestNativeService.vcxproj deleted file mode 100644 index c051214af8ab5c..00000000000000 --- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/System.ServiceProcess.ServiceController.TestNativeService.vcxproj +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - Release - x64 - - - - {CEB0775C-4273-4AC4-B50E-4492718051AE} - Win32Proj - Release - x64 - NativeTestService - $(OutputPath) - - v120 - - - - Application - false - $(DefaultPlatformToolset) - true - Unicode - - - - - - - - - - false - - - - Level3 - NotUsing - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - true - - - Console - true - true - true - - - - - - - PreserveNewest - - - - - - - \ No newline at end of file diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/Configurations.props b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Configurations.props similarity index 89% rename from src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/Configurations.props rename to src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Configurations.props index c398e42e899425..5e52d60de3ca81 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestNativeService/Configurations.props +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Configurations.props @@ -2,7 +2,8 @@ - netstandard; + netcoreapp; + netfx; \ No newline at end of file diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs new file mode 100644 index 00000000000000..e085b6a0c502c4 --- /dev/null +++ b/src/libraries/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: [create|delete]"); + return 1; + } + } +} diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.csproj b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.csproj new file mode 100644 index 00000000000000..f9a346b6d32c2f --- /dev/null +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.csproj @@ -0,0 +1,56 @@ + + + + + {E62B874D-1A0D-41BC-8CFC-9E09D0860A77} + true + exe + + + + + + + + + + Component + + + Common\Interop\Windows\Interop.Libraries.cs + + + Common\Interop\Windows\Interop.ServiceProcessOptions.cs + + + Common\Interop\Windows\Interop.CloseServiceHandle.cs + + + Common\Interop\Windows\Interop.OpenSCManager.cs + + + Common\Interop\Windows\Interop.OpenService.cs + + + Common\Interop\Windows\Interop.CreateService.cs + + + Common\Interop\Windows\Interop.SERVICE_DESCRIPTION.cs + + + Common\Interop\Windows\Interop.ChangeServiceConfig2.cs + + + Common\Interop\Windows\Interop.SERVICE_DELAYED_AUTOSTART_INFO.cs + + + Common\Interop\Windows\Interop.DeleteService.cs + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.runtimeconfig.json b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/System.ServiceProcess.ServiceController.TestService.runtimeconfig.json new file mode 100644 index 00000000000000..573682438bfbcc --- /dev/null +++ b/src/libraries/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/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs new file mode 100644 index 00000000000000..24a8467e3bb085 --- /dev/null +++ b/src/libraries/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/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs new file mode 100644 index 00000000000000..9bd9c128073a7c --- /dev/null +++ b/src/libraries/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(); + + 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/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/System.ServiceProcess.ServiceController.Tests.csproj b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj similarity index 54% rename from src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/System.ServiceProcess.ServiceController.Tests.csproj rename to src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj index 0e9d264310fbb1..55a1762218e120 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/System.ServiceProcess.ServiceController.Tests.csproj +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj @@ -4,24 +4,21 @@ {F7D9984B-02EB-4573-84EF-00FFFBFB872C} - - - + + + + + + CommonTest\System\PlatformDetection.cs - - - {ceb0775c-4273-4ac4-b50e-4492718051ae} - System.ServiceProcess.ServiceController.TestNativeService - OSGroup;TargetGroup;Configuration - Configuration=Release - Platform=x64 - + + \ No newline at end of file diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs new file mode 100644 index 00000000000000..30b499a32426b8 --- /dev/null +++ b/src/libraries/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 s_runningWithElevatedPrivileges = new Lazy( + () => 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(); + } + } + } +} diff --git a/src/libraries/build.proj b/src/libraries/build.proj index e00c9826dc495f..b1a793952c03f2 100644 --- a/src/libraries/build.proj +++ b/src/libraries/build.proj @@ -60,9 +60,6 @@ <_projectsToUpdate Include="$(MSBuildThisFileDirectory)src/**/*.*ilproj" Exclude="@(_projectsToExcludeFromUpdate)" /> <_solutionsToUpdateFiles Include="$(MSBuildThisFileDirectory)src/*/dir.props" /> <_solutionsToUpdate Include="@(_solutionsToUpdateFiles->'%(RootDir)%(Directory)')" Exclude="@(_solutionsToExcludeFromUpdate)" /> - <_solutionsToUpdate Include="$(MSBuildThisFileDirectory)src/System.ServiceProcess.ServiceController/" > - TestNativeService -