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