Skip to content

Commit e00098f

Browse files
authored
Merge pull request #409 from nblumhardt/review-shutdown
Tidy up `ForwardingChannel` shut-down logic
2 parents 735f383 + e6ba3ff commit e00098f

File tree

5 files changed

+156
-243
lines changed

5 files changed

+156
-243
lines changed

seqcli.sln.DotSettings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@
3434
<s:Boolean x:Key="/Default/UserDictionary/Words/=STORAGEPATH/@EntryIndexedValue">True</s:Boolean>
3535
<s:Boolean x:Key="/Default/UserDictionary/Words/=subcommand/@EntryIndexedValue">True</s:Boolean>
3636
<s:Boolean x:Key="/Default/UserDictionary/Words/=syslogdt/@EntryIndexedValue">True</s:Boolean>
37+
<s:Boolean x:Key="/Default/UserDictionary/Words/=tcpip/@EntryIndexedValue">True</s:Boolean>
3738
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tokenizes/@EntryIndexedValue">True</s:Boolean>
3839
<s:Boolean x:Key="/Default/UserDictionary/Words/=trailingindent/@EntryIndexedValue">True</s:Boolean>
3940
<s:Boolean x:Key="/Default/UserDictionary/Words/=unawaited/@EntryIndexedValue">True</s:Boolean>
4041
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unclosable/@EntryIndexedValue">True</s:Boolean>
42+
<s:Boolean x:Key="/Default/UserDictionary/Words/=urlacl/@EntryIndexedValue">True</s:Boolean>
43+
<s:Boolean x:Key="/Default/UserDictionary/Words/=winmgmt/@EntryIndexedValue">True</s:Boolean>
4144
<s:Boolean x:Key="/Default/UserDictionary/Words/=xmpweb/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs

Lines changed: 91 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -17,246 +17,136 @@
1717
using System;
1818
using System.Diagnostics.CodeAnalysis;
1919
using System.IO;
20-
using System.Runtime.InteropServices;
2120
using System.Security.AccessControl;
22-
using System.ServiceProcess;
2321
using System.Threading.Tasks;
2422
using SeqCli.Cli.Features;
2523
using SeqCli.Config;
2624
using SeqCli.Forwarder.Cli.Features;
2725
using SeqCli.Forwarder.ServiceProcess;
2826
using SeqCli.Forwarder.Util;
2927

28+
namespace SeqCli.Cli.Commands.Forwarder;
29+
3030
// ReSharper disable once ClassNeverInstantiated.Global
3131

32-
namespace SeqCli.Cli.Commands.Forwarder
32+
[Command("forwarder", "install", "Install the forwarder as a Windows service", Visibility = FeatureVisibility.Preview)]
33+
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
34+
class InstallCommand : Command
3335
{
34-
[Command("forwarder", "install", "Install the forwarder as a Windows service", Visibility = FeatureVisibility.Preview)]
35-
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
36-
class InstallCommand : Command
36+
readonly StoragePathFeature _storagePath;
37+
readonly ServiceCredentialsFeature _serviceCredentials;
38+
readonly ListenUriFeature _listenUri;
39+
public InstallCommand()
3740
{
38-
readonly StoragePathFeature _storagePath;
39-
readonly ServiceCredentialsFeature _serviceCredentials;
40-
readonly ListenUriFeature _listenUri;
41-
42-
bool _setup;
43-
44-
public InstallCommand()
45-
{
46-
_storagePath = Enable<StoragePathFeature>();
47-
_listenUri = Enable<ListenUriFeature>();
48-
_serviceCredentials = Enable<ServiceCredentialsFeature>();
49-
50-
Options.Add(
51-
"setup",
52-
"Install and start the service only if it does not exist; otherwise reconfigure the binary location",
53-
_ => _setup = true);
54-
}
41+
_storagePath = Enable<StoragePathFeature>();
42+
_listenUri = Enable<ListenUriFeature>();
43+
_serviceCredentials = Enable<ServiceCredentialsFeature>();
44+
}
5545

56-
string ServiceUsername => _serviceCredentials.IsUsernameSpecified ? _serviceCredentials.Username : "NT AUTHORITY\\LocalService";
46+
string ServiceUsername => _serviceCredentials.IsUsernameSpecified ? _serviceCredentials.Username : "NT AUTHORITY\\LocalService";
5747

58-
protected override Task<int> Run()
48+
protected override Task<int> Run()
49+
{
50+
try
5951
{
60-
try
61-
{
62-
if (!_setup)
63-
{
64-
Install();
65-
return Task.FromResult(0);
66-
}
67-
68-
var exit = Setup();
69-
if (exit == 0)
70-
{
71-
Console.ForegroundColor = ConsoleColor.Green;
72-
Console.WriteLine("Setup completed successfully.");
73-
Console.ResetColor();
74-
}
75-
return Task.FromResult(exit);
76-
}
77-
catch (DirectoryNotFoundException dex)
78-
{
79-
Console.WriteLine("Could not install the service, directory not found: " + dex.Message);
80-
return Task.FromResult(-1);
81-
}
82-
catch (Exception ex)
83-
{
84-
Console.WriteLine("Could not install the service: " + ex.Message);
85-
return Task.FromResult(-1);
86-
}
52+
Install();
53+
return Task.FromResult(0);
8754
}
88-
89-
int Setup()
55+
catch (DirectoryNotFoundException dex)
9056
{
91-
ServiceController controller;
92-
try
93-
{
94-
Console.WriteLine($"Checking the status of the {SeqCliForwarderWindowsService.WindowsServiceName} service...");
95-
96-
controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName);
97-
Console.WriteLine("Status is {0}", controller.Status);
98-
}
99-
catch (InvalidOperationException)
100-
{
101-
Install();
102-
var controller2 = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName);
103-
return Start(controller2);
104-
}
105-
106-
Console.WriteLine("Service is installed; checking path and dependency configuration...");
107-
Reconfigure(controller);
108-
109-
return controller.Status != ServiceControllerStatus.Running ? Start(controller) : 0;
57+
Console.WriteLine("Could not install the service, directory not found: " + dex.Message);
58+
return Task.FromResult(-1);
11059
}
111-
112-
static void Reconfigure(ServiceController controller)
60+
catch (Exception ex)
11361
{
114-
var sc = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe");
115-
if (0 != CaptiveProcess.Run(sc, "config \"" + controller.ServiceName + "\" depend= Winmgmt/Tcpip/CryptSvc", Console.WriteLine, Console.WriteLine))
116-
Console.WriteLine("Could not reconfigure service dependencies; ignoring.");
117-
118-
if (!ServiceConfiguration.GetServiceBinaryPath(controller, out var path))
119-
return;
120-
121-
var current = "\"" + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Program.BinaryName) + "\"";
122-
if (path.StartsWith(current))
123-
return;
124-
125-
var seqRun = path.IndexOf(Program.BinaryName + "\" run", StringComparison.OrdinalIgnoreCase);
126-
if (seqRun == -1)
127-
{
128-
Console.WriteLine("Current binary path is an unrecognized format.");
129-
return;
130-
}
131-
132-
Console.WriteLine("Existing service binary path is: {0}", path);
133-
134-
var trimmed = path.Substring((seqRun + Program.BinaryName + " ").Length);
135-
var newPath = current + trimmed;
136-
Console.WriteLine("Updating service binary path configuration to: {0}", newPath);
137-
138-
var escaped = newPath.Replace("\"", "\\\"");
139-
if (0 != CaptiveProcess.Run(sc, "config \"" + controller.ServiceName + "\" binPath= \"" + escaped + "\"", Console.WriteLine, Console.WriteLine))
140-
{
141-
Console.WriteLine("Could not reconfigure service path; ignoring.");
142-
return;
143-
}
144-
145-
Console.WriteLine("Service binary path reconfigured successfully.");
62+
Console.WriteLine("Could not install the service: " + ex.Message);
63+
return Task.FromResult(-1);
14664
}
65+
}
14766

148-
static int Start(ServiceController controller)
149-
{
150-
controller.Start();
151-
152-
if (controller.Status != ServiceControllerStatus.Running)
153-
{
154-
Console.WriteLine("Waiting up to 60 seconds for the service to start (currently: " + controller.Status + ")...");
155-
controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(60));
156-
}
157-
158-
if (controller.Status == ServiceControllerStatus.Running)
159-
{
160-
Console.WriteLine("Started.");
161-
return 0;
162-
}
67+
void Install()
68+
{
69+
Console.WriteLine("Installing service...");
16370

164-
Console.WriteLine("The service hasn't started successfully.");
165-
return -1;
71+
Console.WriteLine($"Updating the configuration in {_storagePath.ConfigFilePath}...");
72+
var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
73+
74+
if (!string.IsNullOrEmpty(_listenUri.ListenUri))
75+
{
76+
config.Forwarder.Api.ListenUri = _listenUri.ListenUri;
77+
SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
16678
}
16779

168-
[DllImport("shlwapi.dll")]
169-
static extern bool PathIsNetworkPath(string pszPath);
170-
171-
void Install()
80+
if (_serviceCredentials.IsUsernameSpecified)
17281
{
173-
Console.WriteLine("Installing service...");
174-
175-
if (PathIsNetworkPath(_storagePath.StorageRootPath))
176-
throw new ArgumentException("Seq requires a local (or SAN) storage location; network shares are not supported.");
177-
178-
Console.WriteLine($"Updating the configuration in {_storagePath.ConfigFilePath}...");
179-
var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
180-
181-
if (!string.IsNullOrEmpty(_listenUri.ListenUri))
182-
{
183-
config.Forwarder.Api.ListenUri = _listenUri.ListenUri;
184-
SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
185-
}
186-
187-
if (_serviceCredentials.IsUsernameSpecified)
188-
{
189-
if (!_serviceCredentials.IsPasswordSpecified)
190-
throw new ArgumentException(
191-
"If a service user account is specified, a password for the account must also be specified.");
192-
193-
// https://technet.microsoft.com/en-us/library/cc794944(v=ws.10).aspx
194-
Console.WriteLine($"Ensuring {_serviceCredentials.Username} is granted 'Log on as a Service' rights...");
195-
AccountRightsHelper.EnsureServiceLogOnRights(_serviceCredentials.Username);
196-
}
82+
if (!_serviceCredentials.IsPasswordSpecified)
83+
throw new ArgumentException(
84+
"If a service user account is specified, a password for the account must also be specified.");
19785

198-
Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.StorageRootPath}...");
199-
GiveFullControl(_storagePath.StorageRootPath);
200-
201-
Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.InternalLogPath}...");
202-
GiveFullControl(_storagePath.InternalLogPath);
86+
// https://technet.microsoft.com/en-us/library/cc794944(v=ws.10).aspx
87+
Console.WriteLine($"Ensuring {_serviceCredentials.Username} is granted 'Log on as a Service' rights...");
88+
AccountRightsHelper.EnsureServiceLogOnRights(_serviceCredentials.Username);
89+
}
20390

204-
var listenUri = MakeListenUriReservationPattern(config.Forwarder.Api.ListenUri);
205-
Console.WriteLine($"Adding URL reservation at {listenUri} for {ServiceUsername}...");
206-
var netshResult = CaptiveProcess.Run("netsh", $"http add urlacl url={listenUri} user=\"{ServiceUsername}\"", Console.WriteLine, Console.WriteLine);
207-
if (netshResult != 0)
208-
Console.WriteLine($"Could not add URL reservation for {listenUri}: `netsh` returned {netshResult}; ignoring");
91+
Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.StorageRootPath}...");
92+
GiveFullControl(_storagePath.StorageRootPath);
20993

210-
var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, Program.BinaryName);
211-
var forwarderRunCmdline = $"\"{exePath}\" run --storage=\"{_storagePath.StorageRootPath}\"";
94+
Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.InternalLogPath}...");
95+
GiveFullControl(_storagePath.InternalLogPath);
21296

213-
var binPath = forwarderRunCmdline.Replace("\"", "\\\"");
97+
var listenUri = MakeListenUriReservationPattern(config.Forwarder.Api.ListenUri);
98+
Console.WriteLine($"Adding URL reservation at {listenUri} for {ServiceUsername}...");
99+
var netshResult = CaptiveProcess.Run("netsh", $"http add urlacl url={listenUri} user=\"{ServiceUsername}\"", Console.WriteLine, Console.WriteLine);
100+
if (netshResult != 0)
101+
Console.WriteLine($"Could not add URL reservation for {listenUri}: `netsh` returned {netshResult}; ignoring");
214102

215-
var scCmdline = "create \"" + SeqCliForwarderWindowsService.WindowsServiceName + "\"" +
216-
" binPath= \"" + binPath + "\"" +
217-
" start= auto" +
218-
" depend= Winmgmt/Tcpip/CryptSvc";
103+
var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Program.BinaryName);
104+
var forwarderRunCmdline = $"\"{exePath}\" forwarder run --pre --storage=\"{_storagePath.StorageRootPath}\"";
219105

220-
if (_serviceCredentials.IsUsernameSpecified)
221-
scCmdline += $" obj= {_serviceCredentials.Username} password= {_serviceCredentials.Password}";
106+
var binPath = forwarderRunCmdline.Replace("\"", "\\\"");
222107

223-
var sc = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe");
224-
if (0 != CaptiveProcess.Run(sc, scCmdline, Console.WriteLine, Console.WriteLine))
225-
{
226-
throw new ArgumentException("Service setup failed");
227-
}
108+
var scCmdline = "create \"" + SeqCliForwarderWindowsService.WindowsServiceName + "\"" +
109+
" binPath= \"" + binPath + "\"" +
110+
" start= auto" +
111+
" depend= Winmgmt/Tcpip/CryptSvc";
228112

229-
Console.WriteLine("Setting service restart policy...");
230-
if (0 != CaptiveProcess.Run(sc, $"failure \"{SeqCliForwarderWindowsService.WindowsServiceName}\" actions= restart/60000/restart/60000/restart/60000// reset= 600000", Console.WriteLine, Console.WriteLine))
231-
Console.WriteLine("Could not set service restart policy; ignoring");
232-
Console.WriteLine("Setting service description...");
233-
if (0 != CaptiveProcess.Run(sc, $"description \"{SeqCliForwarderWindowsService.WindowsServiceName}\" \"Durable storage and forwarding of application log events\"", Console.WriteLine, Console.WriteLine))
234-
Console.WriteLine("Could not set service description; ignoring");
113+
if (_serviceCredentials.IsUsernameSpecified)
114+
scCmdline += $" obj= {_serviceCredentials.Username} password= {_serviceCredentials.Password}";
235115

236-
Console.WriteLine("Service installed successfully.");
116+
var sc = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe");
117+
if (0 != CaptiveProcess.Run(sc, scCmdline, Console.WriteLine, Console.WriteLine))
118+
{
119+
throw new ArgumentException("Service setup failed.");
237120
}
238121

239-
void GiveFullControl(string target)
240-
{
241-
if (target == null) throw new ArgumentNullException(nameof(target));
122+
Console.WriteLine("Setting service restart policy...");
123+
if (0 != CaptiveProcess.Run(sc, $"failure \"{SeqCliForwarderWindowsService.WindowsServiceName}\" actions= restart/60000/restart/60000/restart/60000// reset= 600000", Console.WriteLine, Console.WriteLine))
124+
Console.WriteLine("Could not set service restart policy; ignoring");
125+
Console.WriteLine("Setting service description...");
126+
if (0 != CaptiveProcess.Run(sc, $"description \"{SeqCliForwarderWindowsService.WindowsServiceName}\" \"Durable storage and forwarding of application log events\"", Console.WriteLine, Console.WriteLine))
127+
Console.WriteLine("Could not set service description; ignoring");
242128

243-
if (!Directory.Exists(target))
244-
Directory.CreateDirectory(target);
129+
Console.WriteLine("Service installed successfully.");
130+
}
245131

246-
var storageInfo = new DirectoryInfo(target);
247-
var storageAccessControl = storageInfo.GetAccessControl();
248-
storageAccessControl.AddAccessRule(new FileSystemAccessRule(ServiceUsername,
249-
FileSystemRights.FullControl, AccessControlType.Allow));
250-
storageInfo.SetAccessControl(storageAccessControl);
251-
}
132+
void GiveFullControl(string target)
133+
{
134+
if (!Directory.Exists(target))
135+
Directory.CreateDirectory(target);
136+
137+
var storageInfo = new DirectoryInfo(target);
138+
var storageAccessControl = storageInfo.GetAccessControl();
139+
storageAccessControl.AddAccessRule(new FileSystemAccessRule(ServiceUsername,
140+
FileSystemRights.FullControl, AccessControlType.Allow));
141+
storageInfo.SetAccessControl(storageAccessControl);
142+
}
252143

253-
static string MakeListenUriReservationPattern(string uri)
254-
{
255-
var listenUri = uri.Replace("localhost", "+");
256-
if (!listenUri.EndsWith("/"))
257-
listenUri += "/";
258-
return listenUri;
259-
}
144+
static string MakeListenUriReservationPattern(string uri)
145+
{
146+
var listenUri = uri.Replace("localhost", "+");
147+
if (!listenUri.EndsWith('/'))
148+
listenUri += "/";
149+
return listenUri;
260150
}
261151
}
262152

0 commit comments

Comments
 (0)