-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
Copy pathWindowsServiceTester.cs
158 lines (132 loc) · 6.36 KB
/
WindowsServiceTester.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.ServiceProcess;
using System.Threading.Tasks;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace Microsoft.Extensions.Hosting
{
public class WindowsServiceTester : ServiceController
{
private WindowsServiceTester(SafeServiceHandle serviceHandle, RemoteInvokeHandle remoteInvokeHandle, string serviceName) : base(serviceName)
{
_serviceHandle = serviceHandle;
_remoteInvokeHandle = remoteInvokeHandle;
}
private SafeServiceHandle _serviceHandle;
private RemoteInvokeHandle _remoteInvokeHandle;
public new void Start()
{
Start(Array.Empty<string>());
}
public new void Start(string[] args)
{
base.Start(args);
// get the process
_remoteInvokeHandle.Process.Dispose();
_remoteInvokeHandle.Process = null;
var statusEx = QueryServiceStatusEx();
try
{
_remoteInvokeHandle.Process = Process.GetProcessById(statusEx.dwProcessId);
// fetch the process handle so that we can get the exit code later.
var _ = _remoteInvokeHandle.Process.SafeHandle;
}
catch (ArgumentException)
{ }
}
public static TimeSpan WaitForStatusTimeout { get; set; } = TimeSpan.FromMinutes(3);
public new void WaitForStatus(ServiceControllerStatus desiredStatus) =>
WaitForStatus(desiredStatus, WaitForStatusTimeout);
public new void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout)
{
base.WaitForStatus(desiredStatus, timeout);
Assert.Equal(Status, desiredStatus);
}
// the following overloads are necessary to ensure the compiler will produce the correct signature from a lambda.
public static WindowsServiceTester Create(Func<Task> serviceMain, [CallerMemberName] string serviceName = null) => Create(RemoteExecutor.Invoke(serviceMain, remoteInvokeOptions), serviceName);
public static WindowsServiceTester Create(Func<Task<int>> serviceMain, [CallerMemberName] string serviceName = null) => Create(RemoteExecutor.Invoke(serviceMain, remoteInvokeOptions), serviceName);
public static WindowsServiceTester Create(Func<int> serviceMain, [CallerMemberName] string serviceName = null) => Create(RemoteExecutor.Invoke(serviceMain, remoteInvokeOptions), serviceName);
public static WindowsServiceTester Create(Action serviceMain, [CallerMemberName] string serviceName = null) => Create(RemoteExecutor.Invoke(serviceMain, remoteInvokeOptions), serviceName);
private static RemoteInvokeOptions remoteInvokeOptions = new RemoteInvokeOptions() { Start = false };
private static WindowsServiceTester Create(RemoteInvokeHandle remoteInvokeHandle, string serviceName)
{
// create remote executor commandline arguments
var startInfo = remoteInvokeHandle.Process.StartInfo;
string commandLine = startInfo.FileName + " " + startInfo.Arguments;
// install the service
using (var serviceManagerHandle = new SafeServiceHandle(Interop.Advapi32.OpenSCManager(null, null, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_ALL)))
{
if (serviceManagerHandle.IsInvalid)
{
throw new InvalidOperationException();
}
// delete existing service if it exists
using (var existingServiceHandle = new SafeServiceHandle(Interop.Advapi32.OpenService(serviceManagerHandle, serviceName, Interop.Advapi32.ServiceAccessOptions.ACCESS_TYPE_ALL)))
{
if (!existingServiceHandle.IsInvalid)
{
Interop.Advapi32.DeleteService(existingServiceHandle);
}
}
var serviceHandle = new SafeServiceHandle(
Interop.Advapi32.CreateService(serviceManagerHandle,
serviceName,
$"{nameof(WindowsServiceTester)} {serviceName} test service",
Interop.Advapi32.ServiceAccessOptions.ACCESS_TYPE_ALL,
Interop.Advapi32.ServiceTypeOptions.SERVICE_WIN32_OWN_PROCESS,
(int)ServiceStartMode.Manual,
Interop.Advapi32.ServiceStartErrorModes.ERROR_CONTROL_NORMAL,
commandLine,
loadOrderGroup: null,
pTagId: IntPtr.Zero,
dependencies: null,
servicesStartName: null,
password: null));
if (serviceHandle.IsInvalid)
{
throw new Win32Exception();
}
return new WindowsServiceTester(serviceHandle, remoteInvokeHandle, serviceName);
}
}
internal unsafe Interop.Advapi32.SERVICE_STATUS QueryServiceStatus()
{
Interop.Advapi32.SERVICE_STATUS status = default;
bool success = Interop.Advapi32.QueryServiceStatus(_serviceHandle, &status);
if (!success)
{
throw new Win32Exception();
}
return status;
}
internal unsafe Interop.Advapi32.SERVICE_STATUS_PROCESS QueryServiceStatusEx()
{
Interop.Advapi32.SERVICE_STATUS_PROCESS status = default;
bool success = Interop.Advapi32.QueryServiceStatusEx(_serviceHandle, &status);
if (!success)
{
throw new Win32Exception();
}
return status;
}
protected override void Dispose(bool disposing)
{
if (_remoteInvokeHandle != null)
{
_remoteInvokeHandle.Dispose();
}
if (!_serviceHandle.IsInvalid)
{
// delete the temporary test service
Interop.Advapi32.DeleteService(_serviceHandle);
_serviceHandle.Close();
}
}
}
}