Welcome to mirror list, hosted at ThFree Co, Russian Federation.

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