diff --git a/src/System.ServiceProcess.ServiceController.sln b/src/System.ServiceProcess.ServiceController.sln new file mode 100644 index 000000000000..a6655c975d7b --- /dev/null +++ b/src/System.ServiceProcess.ServiceController.sln @@ -0,0 +1,56 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NativeTestService", "System.ServiceProcess.ServiceController\tests\NativeTestService\NativeTestService.vcxproj", "{CEB0775C-4273-4AC4-B50E-4492718051AE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceProcess.ServiceController.Tests", "System.ServiceProcess.ServiceController\tests\System.ServiceProcess.ServiceController.Tests\System.ServiceProcess.ServiceController.Tests.csproj", "{F7D9984B-02EB-4573-84EF-00FFFBFB872C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceProcess.ServiceController", "System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj", "{F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Debug|Win32.ActiveCfg = Debug|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Debug|Win32.Build.0 = Debug|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Release|Any CPU.ActiveCfg = Release|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Release|Mixed Platforms.Build.0 = Release|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Release|Win32.ActiveCfg = Release|Win32 + {CEB0775C-4273-4AC4-B50E-4492718051AE}.Release|Win32.Build.0 = Release|Win32 + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Debug|Win32.ActiveCfg = Debug|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Any CPU.Build.0 = Release|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F7D9984B-02EB-4573-84EF-00FFFBFB872C}.Release|Win32.ActiveCfg = Release|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Debug|Win32.ActiveCfg = Debug|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Release|Any CPU.Build.0 = Release|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05}.Release|Win32.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/System.ServiceProcess.ServiceController/src/Interop/Interop.Manual.cs b/src/System.ServiceProcess.ServiceController/src/Interop/Interop.Manual.cs new file mode 100644 index 000000000000..6d43787f4b22 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/Interop/Interop.Manual.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; +using System.Text; + +internal static partial class Interop +{ + public const int ERROR_MORE_DATA = 234; + public const int ERROR_INSUFFICIENT_BUFFER = 122; + + public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000; + + public const int ACCEPT_PAUSE_CONTINUE = 0x00000002; + public const int ACCEPT_SHUTDOWN = 0x00000004; + public const int ACCEPT_STOP = 0x00000001; + + public const int CONTROL_CONTINUE = 0x00000003; + public const int CONTROL_PAUSE = 0x00000002; + public const int CONTROL_STOP = 0x00000001; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class ENUM_SERVICE_STATUS + { + public string serviceName = null; + public string displayName = null; + public int serviceType = 0; + public int currentState = 0; + public int controlsAccepted = 0; + public int win32ExitCode = 0; + public int serviceSpecificExitCode = 0; + public int checkPoint = 0; + public int waitHint = 0; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class ENUM_SERVICE_STATUS_PROCESS + { + public string serviceName = null; + public string displayName = null; + public int serviceType = 0; + public int currentState = 0; + public int controlsAccepted = 0; + public int win32ExitCode = 0; + public int serviceSpecificExitCode = 0; + public int checkPoint = 0; + public int waitHint = 0; + public int processID = 0; + public int serviceFlags = 0; + } + + public const int SC_MANAGER_CONNECT = 0x0001; + public const int SC_MANAGER_ENUMERATE_SERVICE = 0x0004; + + public const int SC_ENUM_PROCESS_INFO = 0; + + [StructLayout(LayoutKind.Sequential)] + public struct SERVICE_STATUS + { + public int serviceType; + public int currentState; + public int controlsAccepted; + public int win32ExitCode; + public int serviceSpecificExitCode; + public int checkPoint; + public int waitHint; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe class QUERY_SERVICE_CONFIG + { + public int dwServiceType; + public int dwStartType; + public int dwErrorControl; + public char* lpBinaryPathName; + public char* lpLoadOrderGroup; + public int dwTagId; + public char* lpDependencies; + public char* lpServiceStartName; + public char* lpDisplayName; + } + + public const int SERVICE_QUERY_CONFIG = 0x0001; + public const int SERVICE_CHANGE_CONFIG = 0x0002; + public const int SERVICE_QUERY_STATUS = 0x0004; + public const int SERVICE_ENUMERATE_DEPENDENTS = 0x0008; + public const int SERVICE_START = 0x0010; + public const int SERVICE_STOP = 0x0020; + public const int SERVICE_PAUSE_CONTINUE = 0x0040; + public const int SERVICE_INTERROGATE = 0x0080; + public const int SERVICE_USER_DEFINED_CONTROL = 0x0100; + + public const int SERVICE_ALL_ACCESS = + STANDARD_RIGHTS_REQUIRED | + SERVICE_QUERY_CONFIG | + SERVICE_CHANGE_CONFIG | + SERVICE_QUERY_STATUS | + SERVICE_ENUMERATE_DEPENDENTS | + SERVICE_START | + SERVICE_STOP | + SERVICE_PAUSE_CONTINUE | + SERVICE_INTERROGATE | + SERVICE_USER_DEFINED_CONTROL; + + public const int SERVICE_TYPE_ADAPTER = 0x00000004; + public const int SERVICE_TYPE_FILE_SYSTEM_DRIVER = 0x00000002; + public const int SERVICE_TYPE_INTERACTIVE_PROCESS = 0x00000100; + public const int SERVICE_TYPE_KERNEL_DRIVER = 0x00000001; + public const int SERVICE_TYPE_RECOGNIZER_DRIVER = 0x00000008; + public const int SERVICE_TYPE_WIN32_OWN_PROCESS = 0x00000010; + public const int SERVICE_TYPE_WIN32_SHARE_PROCESS = 0x00000020; + public const int SERVICE_TYPE_WIN32 = + SERVICE_TYPE_WIN32_OWN_PROCESS | + SERVICE_TYPE_WIN32_SHARE_PROCESS; + public const int SERVICE_TYPE_DRIVER = + SERVICE_TYPE_KERNEL_DRIVER | + SERVICE_TYPE_FILE_SYSTEM_DRIVER | + SERVICE_TYPE_RECOGNIZER_DRIVER; + public const int SERVICE_TYPE_ALL = + SERVICE_TYPE_WIN32 | + SERVICE_TYPE_ADAPTER | + SERVICE_TYPE_DRIVER | + SERVICE_TYPE_INTERACTIVE_PROCESS; + + public const int START_TYPE_AUTO = 0x00000002; + public const int START_TYPE_DEMAND = 0x00000003; + public const int START_TYPE_DISABLED = 0x00000004; + + public const int SERVICE_ACTIVE = 1; + public const int SERVICE_INACTIVE = 2; + public const int SERVICE_STATE_ALL = SERVICE_ACTIVE | SERVICE_INACTIVE; + + public const int STATE_CONTINUE_PENDING = 0x00000005; + public const int STATE_PAUSED = 0x00000007; + public const int STATE_PAUSE_PENDING = 0x00000006; + public const int STATE_RUNNING = 0x00000004; + public const int STATE_START_PENDING = 0x00000002; + public const int STATE_STOPPED = 0x00000001; + public const int STATE_STOP_PENDING = 0x00000003; + + public const int STATUS_ACTIVE = 0x00000001; + public const int STATUS_INACTIVE = 0x00000002; + public const int STATUS_ALL = STATUS_ACTIVE | STATUS_INACTIVE; + + public static partial class mincore + { + [DllImport("api-ms-win-service-management-l1-1-0.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public extern static bool CloseServiceHandle(IntPtr handle); + + [DllImport("api-ms-win-service-winsvc-l1-1-0.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public unsafe extern static bool ControlService(IntPtr serviceHandle, int control, SERVICE_STATUS* pStatus); + + [DllImport("api-ms-win-service-core-l1-1-1.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public extern static bool EnumDependentServices(IntPtr serviceHandle, int serviceState, IntPtr bufferOfENUM_SERVICE_STATUS, + int bufSize, ref int bytesNeeded, ref int numEnumerated); + + [DllImport("api-ms-win-service-core-l1-1-1.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public extern static bool EnumServicesStatusEx(IntPtr databaseHandle, int infolevel, int serviceType, int serviceState, + IntPtr status, int size, out int bytesNeeded, out int servicesReturned, ref int resumeHandle, string group); + + [DllImport("api-ms-win-service-management-l1-1-0.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public extern static IntPtr OpenSCManager(string machineName, string databaseName, int access); + + [DllImport("api-ms-win-service-management-l1-1-0.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public extern static IntPtr OpenService(IntPtr databaseHandle, string serviceName, int access); + + [DllImport("api-ms-win-service-management-l2-1-0.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public extern static bool QueryServiceConfig(IntPtr serviceHandle, IntPtr query_service_config_ptr, int bufferSize, out int bytesNeeded); + + [DllImport("api-ms-win-service-winsvc-l1-1-0.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern unsafe bool QueryServiceStatus(IntPtr serviceHandle, SERVICE_STATUS* pStatus); + + [DllImport("api-ms-win-service-management-l1-1-0.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public extern static bool StartService(IntPtr serviceHandle, int argNum, IntPtr argPtrs); + } +} \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs b/src/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs new file mode 100644 index 000000000000..03f59b28ad71 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Security; +using Microsoft.Win32.SafeHandles; +using System.Runtime.InteropServices; + +namespace Microsoft.Win32.SafeHandles +{ + internal class SafeServiceHandle : SafeHandle + { + internal SafeServiceHandle(IntPtr handle) : base(IntPtr.Zero, true) + { + SetHandle(handle); + } + + public override bool IsInvalid + { + get { return DangerousGetHandle() == IntPtr.Zero || DangerousGetHandle() == new IntPtr(-1); } + } + + protected override bool ReleaseHandle() + { + return Interop.mincore.CloseServiceHandle(handle); + } + } +} \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/src/Resources/Strings.Designer.cs b/src/System.ServiceProcess.ServiceController/src/Resources/Strings.Designer.cs new file mode 100644 index 000000000000..3206036db783 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/Resources/Strings.Designer.cs @@ -0,0 +1,208 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Resources { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.ServiceProcess.ServiceController.Resources.Strings", typeof(Strings).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Arguments within the 'args' array passed to Start cannot be null.. + /// + internal static string ArgsCantBeNull { + get { + return ResourceManager.GetString("ArgsCantBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to MachineName value {0} is invalid.. + /// + internal static string BadMachineName { + get { + return ResourceManager.GetString("BadMachineName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot start service {0} on computer '{1}'.. + /// + internal static string CannotStart { + get { + return ResourceManager.GetString("CannotStart", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Display name cannot be null or empty. + /// + internal static string DisplayName { + get { + return ResourceManager.GetString("DisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.. + /// + internal static string InvalidEnumArgument { + get { + return ResourceManager.GetString("InvalidEnumArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid value {1} for parameter {0}.. + /// + internal static string InvalidParameter { + get { + return ResourceManager.GetString("InvalidParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Display name could not be retrieved for service {0} on computer '{1}'.. + /// + internal static string NoDisplayName { + get { + return ResourceManager.GetString("NoDisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to MachineName was not set.. + /// + internal static string NoMachineName { + get { + return ResourceManager.GetString("NoMachineName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Service {0} was not found on computer '{1}'.. + /// + internal static string NoService { + get { + return ResourceManager.GetString("NoService", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot open Service Control Manager on computer '{0}'. This operation might require other privileges.. + /// + internal static string OpenSC { + get { + return ResourceManager.GetString("OpenSC", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot open {0} service on computer '{1}'.. + /// + internal static string OpenService { + get { + return ResourceManager.GetString("OpenService", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot pause {0} service on computer '{1}'.. + /// + internal static string PauseService { + get { + return ResourceManager.GetString("PauseService", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot resume {0} service on computer '{1}'.. + /// + internal static string ResumeService { + get { + return ResourceManager.GetString("ResumeService", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Service name {0} contains invalid characters, is empty, or is too long (max length = {1}).. + /// + internal static string ServiceName { + get { + return ResourceManager.GetString("ServiceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot stop {0} service on computer '{1}'.. + /// + internal static string StopService { + get { + return ResourceManager.GetString("StopService", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Time out has expired and the operation has not been completed.. + /// + internal static string Timeout { + get { + return ResourceManager.GetString("Timeout", resourceCulture); + } + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/src/Resources/Strings.resx b/src/System.ServiceProcess.ServiceController/src/Resources/Strings.resx new file mode 100644 index 000000000000..dc5c0cf14071 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/Resources/Strings.resx @@ -0,0 +1,63 @@ + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Arguments within the 'args' array passed to Start cannot be null. + + + MachineName value {0} is invalid. + + + Cannot start service {0} on computer '{1}'. + + + Display name cannot be null or empty + + + The value of argument '{0}' ({1}) is invalid for Enum type '{2}'. + + + Invalid value {1} for parameter {0}. + + + Display name could not be retrieved for service {0} on computer '{1}'. + + + MachineName was not set. + + + Service {0} was not found on computer '{1}'. + + + Cannot open Service Control Manager on computer '{0}'. This operation might require other privileges. + + + Cannot open {0} service on computer '{1}'. + + + Cannot pause {0} service on computer '{1}'. + + + Cannot resume {0} service on computer '{1}'. + + + Service name {0} contains invalid characters, is empty, or is too long (max length = {1}). + + + Cannot stop {0} service on computer '{1}'. + + + Time out has expired and the operation has not been completed. + + \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj b/src/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj new file mode 100644 index 000000000000..9de6c84928f7 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj @@ -0,0 +1,48 @@ + + + + + Debug + AnyCPU + Library + System.ServiceProcess.ServiceController + System.ServiceProcess.ServiceController + true + {F4821CB6-91A3-4546-BC4F-E00DBFBDAA05} + + + + + + + + + + Common\System\SR.cs + + + + + True + True + Strings.resx + + + + + + + + + + ResXFileCodeGenerator + Strings.Designer.cs + Resources + Designer + + + + + + + \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs new file mode 100644 index 000000000000..a74728eb2401 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs @@ -0,0 +1,801 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license 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.Globalization; +using System.Security; + +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 + { + private readonly string _machineName; + private readonly ManualResetEvent _waitForStatusSignal = new ManualResetEvent(false); + private const string DefaultMachineName = "."; + + private string _name; + private string _eitherName; + private string _displayName; + private int _commandsAccepted; + private bool _statusGenerated; + private int _type; + private bool _disposed; + private SafeServiceHandle _serviceManagerHandle; + private ServiceControllerStatus _status; + private ServiceController[] _dependentServices; + private ServiceController[] _servicesDependedOn; + + private const int SERVICENAMEMAXLENGTH = 80; + private const int DISPLAYNAMEBUFFERSIZE = 256; + + /// Creates a ServiceController object, based on service name. + public ServiceController(string name) + : this(name, DefaultMachineName) + { + } + + /// Creates a ServiceController object, based on machine and service name. + public ServiceController(string name, string machineName) + { + if (!CheckMachineName(machineName)) + throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); + + if (string.IsNullOrEmpty(name)) + throw new ArgumentException(SR.Format(SR.InvalidParameter, "name", name)); + + _machineName = machineName; + _eitherName = name; + _type = Interop.SERVICE_TYPE_ALL; + } + + + /// Used by the GetServices and GetDevices methods. Avoids duplicating work by the static + /// methods and our own GenerateInfo(). + private ServiceController(string machineName, Interop.ENUM_SERVICE_STATUS status) + { + if (!CheckMachineName(machineName)) + throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); + + _machineName = machineName; + _name = status.serviceName; + _displayName = status.displayName; + _commandsAccepted = status.controlsAccepted; + _status = (ServiceControllerStatus)status.currentState; + _type = status.serviceType; + _statusGenerated = true; + } + + /// Used by the GetServicesInGroup method. + private ServiceController(string machineName, Interop.ENUM_SERVICE_STATUS_PROCESS status) + { + if (!CheckMachineName(machineName)) + throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); + + _machineName = machineName; + _name = status.serviceName; + _displayName = status.displayName; + _commandsAccepted = status.controlsAccepted; + _status = (ServiceControllerStatus)status.currentState; + _type = status.serviceType; + _statusGenerated = true; + } + + /// Tells if the service referenced by this object can be paused. + public bool CanPauseAndContinue + { + get + { + GenerateStatus(); + return (_commandsAccepted & Interop.ACCEPT_PAUSE_CONTINUE) != 0; + } + } + + + /// Tells if the service is notified when system shutdown occurs. + public bool CanShutdown + { + get + { + GenerateStatus(); + return (_commandsAccepted & Interop.ACCEPT_SHUTDOWN) != 0; + } + } + + /// Tells if the service referenced by this object can be stopped. + public bool CanStop + { + get + { + GenerateStatus(); + return (_commandsAccepted & Interop.ACCEPT_STOP) != 0; + } + } + + /// The descriptive name shown for this service in the Service applet. + public string DisplayName + { + get + { + if (_displayName == null) + GenerateNames(); + return _displayName; + } + } + + /// The set of services that depend on this service. These are the services that will be stopped if + /// this service is stopped. + public ServiceController[] DependentServices + { + get + { + if (_dependentServices == null) + { + IntPtr serviceHandle = GetServiceHandle(Interop.SERVICE_ENUMERATE_DEPENDENTS); + try + { + // figure out how big a buffer we need to get the info + int bytesNeeded = 0; + int numEnumerated = 0; + bool result = Interop.mincore.EnumDependentServices(serviceHandle, Interop.SERVICE_STATE_ALL, IntPtr.Zero, 0, + ref bytesNeeded, ref numEnumerated); + if (result) + { + _dependentServices = Array.Empty(); + return _dependentServices; + } + + int lastError = Marshal.GetLastWin32Error(); + if (lastError != Interop.ERROR_MORE_DATA) + throw new Win32Exception(lastError); + + // allocate the buffer + IntPtr enumBuffer = Marshal.AllocHGlobal((IntPtr)bytesNeeded); + + try + { + // get all the info + result = Interop.mincore.EnumDependentServices(serviceHandle, Interop.SERVICE_STATE_ALL, enumBuffer, bytesNeeded, + ref bytesNeeded, ref numEnumerated); + if (!result) + throw new Win32Exception(); + + // for each of the entries in the buffer, create a new ServiceController object. + _dependentServices = new ServiceController[numEnumerated]; + for (int i = 0; i < numEnumerated; i++) + { + Interop.ENUM_SERVICE_STATUS status = new Interop.ENUM_SERVICE_STATUS(); + IntPtr structPtr = (IntPtr)((long)enumBuffer + (i * Marshal.SizeOf())); + Marshal.PtrToStructure(structPtr, status); + _dependentServices[i] = new ServiceController(_machineName, status); + } + } + finally + { + Marshal.FreeHGlobal(enumBuffer); + } + } + finally + { + Interop.mincore.CloseServiceHandle(serviceHandle); + } + } + + return _dependentServices; + } + } + + /// The name of the machine on which this service resides. + public string MachineName + { + get + { + return _machineName; + } + } + + /// Returns the short name of the service referenced by this object. + public string ServiceName + { + get + { + if (_name == null) + GenerateNames(); + return _name; + } + } + + public unsafe ServiceController[] ServicesDependedOn + { + get + { + if (_servicesDependedOn != null) + return _servicesDependedOn; + + IntPtr serviceHandle = GetServiceHandle(Interop.SERVICE_QUERY_CONFIG); + try + { + int bytesNeeded = 0; + bool success = Interop.mincore.QueryServiceConfig(serviceHandle, IntPtr.Zero, 0, out bytesNeeded); + if (success) + { + _servicesDependedOn = Array.Empty(); + return _servicesDependedOn; + } + + int lastError = Marshal.GetLastWin32Error(); + if (lastError != Interop.ERROR_INSUFFICIENT_BUFFER) + throw new Win32Exception(lastError); + + // get the info + IntPtr bufPtr = Marshal.AllocHGlobal((IntPtr)bytesNeeded); + try + { + success = Interop.mincore.QueryServiceConfig(serviceHandle, bufPtr, bytesNeeded, out bytesNeeded); + if (!success) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Interop.QUERY_SERVICE_CONFIG config = new Interop.QUERY_SERVICE_CONFIG(); + Marshal.PtrToStructure(bufPtr, config); + Dictionary dependencyHash = null; + + char* dependencyChar = config.lpDependencies; + if (dependencyChar != null) + { + // lpDependencies points to the start of multiple null-terminated strings. The list is + // double-null terminated. + int length = 0; + dependencyHash = new Dictionary(); + while (*(dependencyChar + length) != '\0') + { + length++; + if (*(dependencyChar + length) == '\0') + { + string dependencyNameStr = new string(dependencyChar, 0, length); + dependencyChar = dependencyChar + length + 1; + length = 0; + if (dependencyNameStr.StartsWith("+", StringComparison.Ordinal)) + { + // this entry is actually a service load group + Interop.ENUM_SERVICE_STATUS_PROCESS[] loadGroup = GetServicesInGroup(_machineName, dependencyNameStr.Substring(1)); + foreach (Interop.ENUM_SERVICE_STATUS_PROCESS groupMember in loadGroup) + { + if (!dependencyHash.ContainsKey(groupMember.serviceName)) + dependencyHash.Add(groupMember.serviceName, new ServiceController(MachineName, groupMember)); + } + } + else + { + if (!dependencyHash.ContainsKey(dependencyNameStr)) + dependencyHash.Add(dependencyNameStr, new ServiceController(dependencyNameStr, MachineName)); + } + } + } + } + + if (dependencyHash != null) + { + _servicesDependedOn = new ServiceController[dependencyHash.Count]; + dependencyHash.Values.CopyTo(_servicesDependedOn, 0); + } + else + { + _servicesDependedOn = Array.Empty(); + } + + return _servicesDependedOn; + } + finally + { + Marshal.FreeHGlobal(bufPtr); + } + } + finally + { + Interop.mincore.CloseServiceHandle(serviceHandle); + } + } + } + + public SafeHandle ServiceHandle + { + get + { + return new SafeServiceHandle(GetServiceHandle(Interop.SERVICE_ALL_ACCESS)); + } + } + + /// Gets the status of the service referenced by this object, e.g., Running, Stopped, etc. + public ServiceControllerStatus Status + { + get + { + GenerateStatus(); + return _status; + } + } + + /// Gets the type of service that this object references. + public ServiceType ServiceType + { + get + { + GenerateStatus(); + return (ServiceType)_type; + } + } + + private static bool CheckMachineName(string value) + { + return !string.IsNullOrWhiteSpace(value) && value.IndexOf('\\') == -1; + } + + private void Close() + { + } + + public void Dispose() + { + Dispose(true); + } + + /// Disconnects this object from the service and frees any allocated resources. + protected virtual void Dispose(bool disposing) + { + if (_serviceManagerHandle != null) + { + _serviceManagerHandle.Dispose(); + _serviceManagerHandle = null; + } + + _statusGenerated = false; + _type = Interop.SERVICE_TYPE_ALL; + _disposed = true; + } + + private unsafe void GenerateStatus() + { + if (!_statusGenerated) + { + IntPtr serviceHandle = GetServiceHandle(Interop.SERVICE_QUERY_STATUS); + try + { + Interop.SERVICE_STATUS svcStatus = new Interop.SERVICE_STATUS(); + bool success = Interop.mincore.QueryServiceStatus(serviceHandle, &svcStatus); + if (!success) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + _commandsAccepted = svcStatus.controlsAccepted; + _status = (ServiceControllerStatus)svcStatus.currentState; + _type = svcStatus.serviceType; + _statusGenerated = true; + } + finally + { + Interop.mincore.CloseServiceHandle(serviceHandle); + } + } + } + + private unsafe void GenerateNames() + { + if (_machineName.Length == 0) + throw new ArgumentException(SR.NoMachineName); + + IntPtr databaseHandle = IntPtr.Zero; + IntPtr memory = IntPtr.Zero; + int bytesNeeded; + int servicesReturned; + int resumeHandle = 0; + + try + { + databaseHandle = GetDataBaseHandleWithEnumerateAccess(_machineName); + Interop.mincore.EnumServicesStatusEx( + databaseHandle, + Interop.SC_ENUM_PROCESS_INFO, + Interop.SERVICE_TYPE_WIN32, + Interop.STATUS_ALL, + IntPtr.Zero, + 0, + out bytesNeeded, + out servicesReturned, + ref resumeHandle, + null); + + memory = Marshal.AllocHGlobal(bytesNeeded); + + Interop.mincore.EnumServicesStatusEx( + databaseHandle, + Interop.SC_ENUM_PROCESS_INFO, + Interop.SERVICE_TYPE_WIN32, + Interop.STATUS_ALL, + memory, + bytesNeeded, + out bytesNeeded, + out servicesReturned, + ref resumeHandle, + null); + + // Since the service name of one service cannot be equal to the + // service or display name of another service, we can safely + // loop through all services checking if either the service or + // display name matches the user given name. If there is a + // match, then we've found the service. + for (int i = 0; i < servicesReturned; i++) + { + IntPtr structPtr = (IntPtr)((long)memory + (i * Marshal.SizeOf())); + Interop.ENUM_SERVICE_STATUS_PROCESS status = new Interop.ENUM_SERVICE_STATUS_PROCESS(); + Marshal.PtrToStructure(structPtr, status); + + if (string.Equals(_eitherName, status.serviceName, StringComparison.OrdinalIgnoreCase) || + string.Equals(_eitherName, status.displayName, StringComparison.OrdinalIgnoreCase)) + { + if (_name == null) + { + _name = status.serviceName; + } + + if (_displayName == null) + { + _displayName = status.displayName; + } + + _eitherName = string.Empty; + return; + } + } + + throw new InvalidOperationException(SR.Format(SR.NoService, _eitherName, _machineName)); + } + finally + { + Marshal.FreeHGlobal(memory); + if (databaseHandle != IntPtr.Zero) + { + Interop.mincore.CloseServiceHandle(databaseHandle); + } + } + } + + private static IntPtr GetDataBaseHandleWithAccess(string machineName, int serviceControlManagerAccess) + { + IntPtr databaseHandle = IntPtr.Zero; + if (machineName.Equals(DefaultMachineName) || machineName.Length == 0) + { + databaseHandle = Interop.mincore.OpenSCManager(null, null, serviceControlManagerAccess); + } + else + { + databaseHandle = Interop.mincore.OpenSCManager(machineName, null, serviceControlManagerAccess); + } + + if (databaseHandle == IntPtr.Zero) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.OpenSC, machineName), inner); + } + + return databaseHandle; + } + + private void GetDataBaseHandleWithConnectAccess() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + + // get a handle to SCM with connect access and store it in serviceManagerHandle field. + if (_serviceManagerHandle == null) + { + _serviceManagerHandle = new SafeServiceHandle(GetDataBaseHandleWithAccess(_machineName, Interop.SC_MANAGER_CONNECT)); + } + } + + private static IntPtr GetDataBaseHandleWithEnumerateAccess(string machineName) + { + return GetDataBaseHandleWithAccess(machineName, Interop.SC_MANAGER_ENUMERATE_SERVICE); + } + + /// Gets all the device-driver services on the local machine. + public static ServiceController[] GetDevices() + { + return GetDevices(DefaultMachineName); + } + + /// Gets all the device-driver services in the machine specified. + public static ServiceController[] GetDevices(string machineName) + { + return GetServicesOfType(machineName, Interop.SERVICE_TYPE_DRIVER); + } + + /// Opens a handle for the current service. The handle must be closed with + /// a call to Interop.CloseServiceHandle(). + private IntPtr GetServiceHandle(int desiredAccess) + { + GetDataBaseHandleWithConnectAccess(); + + IntPtr serviceHandle = Interop.mincore.OpenService(_serviceManagerHandle.DangerousGetHandle(), ServiceName, desiredAccess); + if (serviceHandle == IntPtr.Zero) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.OpenService, ServiceName, _machineName), inner); + } + + return serviceHandle; + } + + /// Gets the services (not including device-driver services) on the local machine. + public static ServiceController[] GetServices() + { + return GetServices(DefaultMachineName); + } + + /// Gets the services (not including device-driver services) on the machine specified. + public static ServiceController[] GetServices(string machineName) + { + return GetServicesOfType(machineName, Interop.SERVICE_TYPE_WIN32); + } + + /// Helper function for ServicesDependedOn. + private static Interop.ENUM_SERVICE_STATUS_PROCESS[] GetServicesInGroup(string machineName, string group) + { + return GetServices(machineName, Interop.SERVICE_TYPE_WIN32, group, status => { return status; }); + } + + /// Helper function for GetDevices and GetServices. + private static ServiceController[] GetServicesOfType(string machineName, int serviceType) + { + if (!CheckMachineName(machineName)) + throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); + + return GetServices(machineName, serviceType, null, status => { return new ServiceController(machineName, status); }); + } + + /// Helper for GetDevices, GetServices, and ServicesDependedOn + private static T[] GetServices(string machineName, int serviceType, string group, Func selector) + { + IntPtr databaseHandle = IntPtr.Zero; + IntPtr memory = IntPtr.Zero; + int bytesNeeded; + int servicesReturned; + int resumeHandle = 0; + + T[] services; + + try + { + databaseHandle = GetDataBaseHandleWithEnumerateAccess(machineName); + Interop.mincore.EnumServicesStatusEx( + databaseHandle, + Interop.SC_ENUM_PROCESS_INFO, + serviceType, + Interop.STATUS_ALL, + IntPtr.Zero, + 0, + out bytesNeeded, + out servicesReturned, + ref resumeHandle, + group); + + memory = Marshal.AllocHGlobal((IntPtr)bytesNeeded); + + // + // Get the set of services + // + Interop.mincore.EnumServicesStatusEx( + databaseHandle, + Interop.SC_ENUM_PROCESS_INFO, + serviceType, + Interop.STATUS_ALL, + memory, + bytesNeeded, + out bytesNeeded, + out servicesReturned, + ref resumeHandle, + group); + + // + // Go through the block of memory it returned to us and select the results + // + services = new T[servicesReturned]; + for (int i = 0; i < servicesReturned; i++) + { + IntPtr structPtr = (IntPtr)((long)memory + (i * Marshal.SizeOf())); + Interop.ENUM_SERVICE_STATUS_PROCESS status = new Interop.ENUM_SERVICE_STATUS_PROCESS(); + Marshal.PtrToStructure(structPtr, status); + services[i] = selector(status); + } + } + finally + { + Marshal.FreeHGlobal(memory); + + if (databaseHandle != IntPtr.Zero) + { + Interop.mincore.CloseServiceHandle(databaseHandle); + } + } + + return services; + } + + /// Suspends a service's operation. + public unsafe void Pause() + { + IntPtr serviceHandle = GetServiceHandle(Interop.SERVICE_PAUSE_CONTINUE); + try + { + Interop.SERVICE_STATUS status = new Interop.SERVICE_STATUS(); + bool result = Interop.mincore.ControlService(serviceHandle, Interop.CONTROL_PAUSE, &status); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.PauseService, ServiceName, _machineName), inner); + } + } + finally + { + Interop.mincore.CloseServiceHandle(serviceHandle); + } + } + + /// Continues a service after it has been paused. + public unsafe void Continue() + { + IntPtr serviceHandle = GetServiceHandle(Interop.SERVICE_PAUSE_CONTINUE); + try + { + Interop.SERVICE_STATUS status = new Interop.SERVICE_STATUS(); + bool result = Interop.mincore.ControlService(serviceHandle, Interop.CONTROL_CONTINUE, &status); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.ResumeService, ServiceName, _machineName), inner); + } + } + finally + { + Interop.mincore.CloseServiceHandle(serviceHandle); + } + } + + /// Refreshes all property values. + public void Refresh() + { + _statusGenerated = false; + _dependentServices = null; + _servicesDependedOn = null; + } + + /// Starts the service. + public void Start() + { + Start(new string[0]); + } + + /// Starts a service in the machine specified. + public void Start(string[] args) + { + if (args == null) + throw new ArgumentNullException("args"); + + IntPtr serviceHandle = GetServiceHandle(Interop.SERVICE_START); + + try + { + IntPtr[] argPtrs = new IntPtr[args.Length]; + int i = 0; + try + { + for (i = 0; i < args.Length; i++) + { + if (args[i] == null) + throw new ArgumentNullException(SR.ArgsCantBeNull, "args"); + + argPtrs[i] = Marshal.StringToHGlobalUni(args[i]); + } + } + catch + { + for (int j = 0; j < i; j++) + Marshal.FreeHGlobal(argPtrs[i]); + throw; + } + + GCHandle argPtrsHandle = new GCHandle(); + try + { + argPtrsHandle = GCHandle.Alloc(argPtrs, GCHandleType.Pinned); + bool result = Interop.mincore.StartService(serviceHandle, args.Length, (IntPtr)argPtrsHandle.AddrOfPinnedObject()); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.CannotStart, ServiceName, _machineName), inner); + } + } + finally + { + for (i = 0; i < args.Length; i++) + Marshal.FreeHGlobal(argPtrs[i]); + if (argPtrsHandle.IsAllocated) + argPtrsHandle.Free(); + } + } + finally + { + Interop.mincore.CloseServiceHandle(serviceHandle); + } + } + + /// Stops the service. If any other services depend on this one for operation, + /// they will be stopped first. The DependentServices property lists this set + /// of services. + public unsafe void Stop() + { + IntPtr serviceHandle = GetServiceHandle(Interop.SERVICE_STOP); + + try + { + // Before stopping this service, stop all the dependent services that are running. + // (It's OK not to cache the result of getting the DependentServices property because it caches on its own.) + for (int i = 0; i < DependentServices.Length; i++) + { + ServiceController currentDependent = DependentServices[i]; + currentDependent.Refresh(); + if (currentDependent.Status != ServiceControllerStatus.Stopped) + { + currentDependent.Stop(); + currentDependent.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 30)); + } + } + + Interop.SERVICE_STATUS status = new Interop.SERVICE_STATUS(); + bool result = Interop.mincore.ControlService(serviceHandle, Interop.CONTROL_STOP, &status); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.StopService, ServiceName, _machineName), inner); + } + } + finally + { + Interop.mincore.CloseServiceHandle(serviceHandle); + } + } + + /// Waits infinitely until the service has reached the given status. + public void WaitForStatus(ServiceControllerStatus desiredStatus) + { + WaitForStatus(desiredStatus, TimeSpan.MaxValue); + } + + /// Waits until the service has reached the given status or until the specified time + /// has expired + public void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout) + { + if (!Enum.IsDefined(typeof(ServiceControllerStatus), desiredStatus)) + throw new ArgumentException(SR.Format(SR.InvalidEnumArgument, "desiredStatus", (int)desiredStatus, typeof(ServiceControllerStatus))); + + DateTime start = DateTime.UtcNow; + Refresh(); + while (Status != desiredStatus) + { + if (DateTime.UtcNow - start > timeout) + throw new System.ServiceProcess.TimeoutException(SR.Timeout); + + _waitForStatusSignal.WaitOne(250); + Refresh(); + } + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceControllerStatus.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceControllerStatus.cs new file mode 100644 index 000000000000..79f20dff191d --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceControllerStatus.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.ServiceProcess +{ + public enum ServiceControllerStatus + { + ContinuePending = Interop.STATE_CONTINUE_PENDING, + Paused = Interop.STATE_PAUSED, + PausePending = Interop.STATE_PAUSE_PENDING, + Running = Interop.STATE_RUNNING, + StartPending = Interop.STATE_START_PENDING, + Stopped = Interop.STATE_STOPPED, + StopPending = Interop.STATE_STOP_PENDING + } +} diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceStartMode.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceStartMode.cs new file mode 100644 index 000000000000..62384ba26f9a --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceStartMode.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.ServiceProcess +{ + public enum ServiceStartMode + { + Manual = Interop.START_TYPE_DEMAND, + Automatic = Interop.START_TYPE_AUTO, + Disabled = Interop.START_TYPE_DISABLED, + } +} diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceType.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceType.cs new file mode 100644 index 000000000000..cc8ef3a8ae12 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceType.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.ServiceProcess +{ + [Flags] + public enum ServiceType + { + Adapter = Interop.SERVICE_TYPE_ADAPTER, + FileSystemDriver = Interop.SERVICE_TYPE_FILE_SYSTEM_DRIVER, + InteractiveProcess = Interop.SERVICE_TYPE_INTERACTIVE_PROCESS, + KernelDriver = Interop.SERVICE_TYPE_KERNEL_DRIVER, + RecognizerDriver = Interop.SERVICE_TYPE_RECOGNIZER_DRIVER, + Win32OwnProcess = Interop.SERVICE_TYPE_WIN32_OWN_PROCESS, + Win32ShareProcess = Interop.SERVICE_TYPE_WIN32_SHARE_PROCESS + } +} diff --git a/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs new file mode 100644 index 000000000000..70c02f1fc92e --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace System.ServiceProcess +{ + public class TimeoutException : Exception + { + private const int ServiceControllerTimeout = unchecked((int)0x80131906); + + public TimeoutException() : base() + { + HResult = ServiceControllerTimeout; + } + + public TimeoutException(string message) : base(message) + { + HResult = ServiceControllerTimeout; + } + + public TimeoutException(String message, Exception innerException) + : base(message, innerException) + { + HResult = ServiceControllerTimeout; + } + } +} diff --git a/src/System.ServiceProcess.ServiceController/src/packages.config b/src/System.ServiceProcess.ServiceController/src/packages.config new file mode 100644 index 000000000000..5c7b8a76a6b8 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/src/packages.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.cpp b/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.cpp new file mode 100644 index 000000000000..807ab77460f3 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.cpp @@ -0,0 +1,677 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// This is a simple Win32 service which does nothing except respond to control +// events by settings its status appropriately. It takes its own service and +// service display name as parameters to allow many tests to spin up their own +// instances of this service in parallel. +// +// Because this will eventually run on OneCore on systems which may not have +// sc.exe, this also provides a means to create, start, stop, and delete this +// service via the command line: +// +// - When run like this: +// +// NativeTestService.exe "TestService" "Test Service" create +// +// This creates a service named "TestService" with display name +// "Test Service", and starts the service. This also creates additional +// instances of this service which depend on "TestService" for the purposes +// of testing. +// +// - When run like this: +// +// NativeTestService.exe "TestService" "Test Service" delete +// +// This attempts to stop "TestService" and delete it and its dependent +// services. +// +// - When run like this: +// +// NativeTestService.exe "TestService" "Test Service" +// +// This executable assumes it is being run as a service, and simply waits +// for and responds to control events. +// + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include +#include + +#define DEPENDENT_SERVICES 2 + +// 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, 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: NativeTestService.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); + 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()); + + 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(), + dependencies.c_str()); + + if (hDependentService == NULL) + { + CloseServiceHandle(hScManager); + return false; + } + } + + // 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, 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 + SERVICE_DEMAND_START, // 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. + + WaitForSingleObject(ghServiceStopEvent, INFINITE); + + // We're stopping, delete the log file + DWORD error = DeleteLogFile(); + + 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(); + LogMessage(L"warning: Failed to delete log file '%s' (%d)\n", gLogFilePath.c_str(), error); + 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); +} \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.vcxproj b/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.vcxproj new file mode 100644 index 000000000000..bcfe8be71e79 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.vcxproj @@ -0,0 +1,85 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {CEB0775C-4273-4AC4-B50E-4492718051AE} + Win32Proj + NativeTestService + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + $(ProjectDir)\$(Configuration)\ + + + false + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.vcxproj.filters b/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.vcxproj.filters new file mode 100644 index 000000000000..6a266db4e270 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/NativeTestService/NativeTestService.vcxproj.filters @@ -0,0 +1,14 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/NativeTestService.exe b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/NativeTestService.exe new file mode 100644 index 000000000000..b3a7f75ad477 Binary files /dev/null and b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/NativeTestService.exe differ diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/ServiceControllerTests.cs b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/ServiceControllerTests.cs new file mode 100644 index 000000000000..f2901044ff21 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/ServiceControllerTests.cs @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.ServiceProcess; +using System.Threading; +using Xunit; +using Microsoft.Win32; + +namespace System.ServiceProcessServiceController.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(3); + 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) + { + var process = new Process(); + process.StartInfo.FileName = "NativeTestService.exe"; + process.StartInfo.Arguments = string.Format("\"{0}\" \"{1}\" {2}", TestServiceName, TestServiceDisplayName, action); + process.Start(); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new Exception("error: NativeTestService.exe failed with exit code " + process.ExitCode.ToString()); + } + } + } + + public class ServiceControllerTests : IDisposable + { + ServiceProvider _testService; + + public ServiceControllerTests() + { + _testService = new ServiceProvider(); + } + + [Fact] + public void ConstructWithServiceName() + { + var controller = new ServiceController(_testService.TestServiceName); + Assert.Equal(_testService.TestServiceName, controller.ServiceName); + Assert.Equal(_testService.TestServiceDisplayName, controller.DisplayName); + Assert.Equal(_testService.TestMachineName, controller.MachineName); + Assert.Equal(ServiceType.Win32OwnProcess, controller.ServiceType); + } + + [Fact] + public void ConstructWithDisplayName() + { + var controller = new ServiceController(_testService.TestServiceDisplayName); + Assert.Equal(_testService.TestServiceName, controller.ServiceName); + Assert.Equal(_testService.TestServiceDisplayName, controller.DisplayName); + Assert.Equal(_testService.TestMachineName, controller.MachineName); + Assert.Equal(ServiceType.Win32OwnProcess, controller.ServiceType); + } + + [Fact] + public void ConstructWithMachineName() + { + var controller = new ServiceController(_testService.TestServiceName, _testService.TestMachineName); + Assert.Equal(_testService.TestServiceName, controller.ServiceName); + Assert.Equal(_testService.TestServiceDisplayName, controller.DisplayName); + Assert.Equal(_testService.TestMachineName, controller.MachineName); + Assert.Equal(ServiceType.Win32OwnProcess, controller.ServiceType); + + Assert.Throws(() => { var c = new ServiceController(_testService.TestServiceName, ""); }); + } + + [Fact] + public void ControlCapabilities() + { + var controller = new ServiceController(_testService.TestServiceName); + Assert.True(controller.CanStop); + Assert.True(controller.CanPauseAndContinue); + Assert.False(controller.CanShutdown); + } + + [Fact] + public void StartWithArguments() + { + var controller = new ServiceController(_testService.TestServiceName); + Assert.Equal(ServiceControllerStatus.Running, controller.Status); + + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped, _testService.ControlTimeout); + Assert.Equal(ServiceControllerStatus.Stopped, controller.Status); + + var args = new[] { "a", "b", "c", "d", "e" }; + controller.Start(args); + 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); + } + + [Fact] + public void StopAndStart() + { + var controller = new ServiceController(_testService.TestServiceName); + Assert.Equal(ServiceControllerStatus.Running, controller.Status); + + for (int i = 0; i < 2; i++) + { + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped, _testService.ControlTimeout); + Assert.Equal(ServiceControllerStatus.Stopped, controller.Status); + + controller.Start(); + controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout); + Assert.Equal(ServiceControllerStatus.Running, controller.Status); + } + } + + [Fact] + public void PauseAndContinue() + { + var controller = new ServiceController(_testService.TestServiceName); + Assert.Equal(ServiceControllerStatus.Running, controller.Status); + + for (int i = 0; i < 2; i++) + { + controller.Pause(); + controller.WaitForStatus(ServiceControllerStatus.Paused, _testService.ControlTimeout); + Assert.Equal(ServiceControllerStatus.Paused, controller.Status); + + controller.Continue(); + controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout); + Assert.Equal(ServiceControllerStatus.Running, controller.Status); + } + } + + [Fact] + public void WaitForStatusTimeout() + { + var controller = new ServiceController(_testService.TestServiceName); + Assert.Throws(() => controller.WaitForStatus(ServiceControllerStatus.Paused, TimeSpan.Zero)); + } + + [Fact] + public void GetServices() + { + bool foundTestService = false; + + foreach (var service in ServiceController.GetServices()) + { + if (service.ServiceName == _testService.TestServiceName) + { + foundTestService = true; + } + } + + Assert.True(foundTestService, "Test service was not enumerated with all services"); + } + + [Fact] + public void GetDevices() + { + var devices = ServiceController.GetDevices(); + Assert.True(devices.Length != 0); + + const ServiceType SERVICE_TYPE_DRIVER = + ServiceType.FileSystemDriver | + ServiceType.KernelDriver | + ServiceType.RecognizerDriver; + + foreach (var device in devices) + { + if ((int)(device.ServiceType & SERVICE_TYPE_DRIVER) == 0) + { + Assert.True(false, string.Format("Service '{0}' is of type '{1}' and is not a device driver.", device.ServiceName, device.ServiceType)); + } + } + } + + [Fact] + public void DependentServices() + { + // The test service creates a number of dependent services, each of which has no dependent services + var controller = new ServiceController(_testService.TestServiceName); + Assert.True(controller.DependentServices.Length > 0); + + for (int i = 0; i < controller.DependentServices.Length; i++) + { + var dependent = controller.DependentServices[i]; + Assert.True(dependent.ServiceName.StartsWith(_testService.DependentTestServiceNamePrefix)); + Assert.True(dependent.DisplayName.StartsWith(_testService.DependentTestServiceDisplayNamePrefix)); + Assert.Equal(ServiceType.Win32OwnProcess, dependent.ServiceType); + Assert.Equal(0, dependent.DependentServices.Length); + } + } + + [Fact] + public void ServicesDependedOn() + { + // The test service creates a number of dependent services, each of these should depend on the test service + var controller = new ServiceController(_testService.TestServiceName); + Assert.True(controller.DependentServices.Length > 0); + + for (int i = 0; i < controller.DependentServices.Length; i++) + { + var dependent = controller.DependentServices[i]; + Assert.True(dependent.ServicesDependedOn.Length == 1); + + var dependency = dependent.ServicesDependedOn[0]; + Assert.Equal(_testService.TestServiceName, dependency.ServiceName); + Assert.Equal(_testService.TestServiceDisplayName, dependency.DisplayName); + } + } + + public void Dispose() + { + _testService.DeleteTestServices(); + } + } +} \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/System.ServiceProcess.ServiceController.Tests.csproj b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/System.ServiceProcess.ServiceController.Tests.csproj new file mode 100644 index 000000000000..aae84569fa67 --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/System.ServiceProcess.ServiceController.Tests.csproj @@ -0,0 +1,38 @@ + + + + + Debug + AnyCPU + Library + System.ServiceProcess.ServiceController.Tests + System.ServiceProcess.ServiceController.Tests + {F7D9984B-02EB-4573-84EF-00FFFBFB872C} + + False + + + + + + + + + + + + + + + {f4821cb6-91a3-4546-bc4f-e00dbfbdaa05} + System.ServiceProcess.ServiceController + + + + + PreserveNewest + + + + + \ No newline at end of file diff --git a/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/packages.config b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/packages.config new file mode 100644 index 000000000000..60511a46241e --- /dev/null +++ b/src/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests/packages.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file