Skip to content

Commit f2156a7

Browse files
committed
Add some top level exception handling to ForwardingChannel's pump tasks, opportunistically fix some path issues and simplify forwarder install.
1 parent cbdd91a commit f2156a7

File tree

5 files changed

+146
-234
lines changed

5 files changed

+146
-234
lines changed

seqcli.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@
3838
<s:Boolean x:Key="/Default/UserDictionary/Words/=trailingindent/@EntryIndexedValue">True</s:Boolean>
3939
<s:Boolean x:Key="/Default/UserDictionary/Words/=unawaited/@EntryIndexedValue">True</s:Boolean>
4040
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unclosable/@EntryIndexedValue">True</s:Boolean>
41+
<s:Boolean x:Key="/Default/UserDictionary/Words/=urlacl/@EntryIndexedValue">True</s:Boolean>
4142
<s:Boolean x:Key="/Default/UserDictionary/Words/=xmpweb/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

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

Lines changed: 93 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
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;
@@ -29,234 +27,129 @@
2927

3028
// ReSharper disable once ClassNeverInstantiated.Global
3129

32-
namespace SeqCli.Cli.Commands.Forwarder
33-
{
34-
[Command("forwarder", "install", "Install the forwarder as a Windows service", IsPreview = true)]
35-
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
36-
class InstallCommand : Command
37-
{
38-
readonly StoragePathFeature _storagePath;
39-
readonly ServiceCredentialsFeature _serviceCredentials;
40-
readonly ListenUriFeature _listenUri;
41-
42-
bool _setup;
30+
namespace SeqCli.Cli.Commands.Forwarder;
4331

44-
public InstallCommand()
45-
{
46-
_storagePath = Enable<StoragePathFeature>();
47-
_listenUri = Enable<ListenUriFeature>();
48-
_serviceCredentials = Enable<ServiceCredentialsFeature>();
32+
[Command("forwarder", "install", "Install the forwarder as a Windows service", IsPreview = true)]
33+
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
34+
class InstallCommand : Command
35+
{
36+
readonly StoragePathFeature _storagePath;
37+
readonly ServiceCredentialsFeature _serviceCredentials;
38+
readonly ListenUriFeature _listenUri;
4939

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-
}
40+
public InstallCommand()
41+
{
42+
_storagePath = Enable<StoragePathFeature>();
43+
_listenUri = Enable<ListenUriFeature>();
44+
_serviceCredentials = Enable<ServiceCredentialsFeature>();
45+
}
5546

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

58-
protected override Task<int> Run()
49+
protected override Task<int> Run()
50+
{
51+
try
5952
{
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-
}
53+
Install();
54+
return Task.FromResult(0);
8755
}
88-
89-
int Setup()
56+
catch (DirectoryNotFoundException dex)
9057
{
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;
58+
Console.WriteLine("Could not install the service, directory not found: " + dex.Message);
59+
return Task.FromResult(-1);
11060
}
111-
112-
static void Reconfigure(ServiceController controller)
61+
catch (Exception ex)
11362
{
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.");
63+
Console.WriteLine("Could not install the service: " + ex.Message);
64+
return Task.FromResult(-1);
14665
}
66+
}
14767

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-
}
68+
void Install()
69+
{
70+
Console.WriteLine("Installing service...");
16371

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

168-
[DllImport("shlwapi.dll")]
169-
static extern bool PathIsNetworkPath(string pszPath);
170-
171-
void Install()
81+
if (_serviceCredentials.IsUsernameSpecified)
17282
{
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-
}
83+
if (!_serviceCredentials.IsPasswordSpecified)
84+
throw new ArgumentException(
85+
"If a service user account is specified, a password for the account must also be specified.");
18686

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-
}
87+
// https://technet.microsoft.com/en-us/library/cc794944(v=ws.10).aspx
88+
Console.WriteLine($"Ensuring {_serviceCredentials.Username} is granted 'Log on as a Service' rights...");
89+
AccountRightsHelper.EnsureServiceLogOnRights(_serviceCredentials.Username);
90+
}
19791

198-
Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.StorageRootPath}...");
199-
GiveFullControl(_storagePath.StorageRootPath);
92+
Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.StorageRootPath}...");
93+
GiveFullControl(_storagePath.StorageRootPath);
20094

201-
Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.InternalLogPath}...");
202-
GiveFullControl(_storagePath.InternalLogPath);
95+
Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.InternalLogPath}...");
96+
GiveFullControl(_storagePath.InternalLogPath);
20397

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");
98+
var listenUri = MakeListenUriReservationPattern(config.Forwarder.Api.ListenUri);
99+
Console.WriteLine($"Adding URL reservation at {listenUri} for {ServiceUsername}...");
100+
var netshResult = CaptiveProcess.Run("netsh", $"http add urlacl url={listenUri} user=\"{ServiceUsername}\"", Console.WriteLine, Console.WriteLine);
101+
if (netshResult != 0)
102+
Console.WriteLine($"Could not add URL reservation for {listenUri}: `netsh` returned {netshResult}; ignoring");
209103

210-
var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, Program.BinaryName);
211-
var forwarderRunCmdline = $"\"{exePath}\" run --storage=\"{_storagePath.StorageRootPath}\"";
104+
var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, Program.BinaryName);
105+
var forwarderRunCmdline = $"\"{exePath}\" forwarder run --pre --storage=\"{_storagePath.StorageRootPath}\"";
212106

213-
var binPath = forwarderRunCmdline.Replace("\"", "\\\"");
107+
var binPath = forwarderRunCmdline.Replace("\"", "\\\"");
214108

215-
var scCmdline = "create \"" + SeqCliForwarderWindowsService.WindowsServiceName + "\"" +
216-
" binPath= \"" + binPath + "\"" +
217-
" start= auto" +
218-
" depend= Winmgmt/Tcpip/CryptSvc";
109+
var scCmdline = "create \"" + SeqCliForwarderWindowsService.WindowsServiceName + "\"" +
110+
" binPath= \"" + binPath + "\"" +
111+
" start= auto" +
112+
" depend= Winmgmt/Tcpip/CryptSvc";
219113

220-
if (_serviceCredentials.IsUsernameSpecified)
221-
scCmdline += $" obj= {_serviceCredentials.Username} password= {_serviceCredentials.Password}";
114+
if (_serviceCredentials.IsUsernameSpecified)
115+
scCmdline += $" obj= {_serviceCredentials.Username} password= {_serviceCredentials.Password}";
222116

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-
}
117+
var sc = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe");
118+
if (0 != CaptiveProcess.Run(sc, scCmdline, Console.WriteLine, Console.WriteLine))
119+
{
120+
throw new ArgumentException("Service setup failed");
121+
}
228122

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");
123+
Console.WriteLine("Setting service restart policy...");
124+
if (0 != CaptiveProcess.Run(sc, $"failure \"{SeqCliForwarderWindowsService.WindowsServiceName}\" actions= restart/60000/restart/60000/restart/60000// reset= 600000", Console.WriteLine, Console.WriteLine))
125+
Console.WriteLine("Could not set service restart policy; ignoring");
126+
Console.WriteLine("Setting service description...");
127+
if (0 != CaptiveProcess.Run(sc, $"description \"{SeqCliForwarderWindowsService.WindowsServiceName}\" \"Durable storage and forwarding of application log events\"", Console.WriteLine, Console.WriteLine))
128+
Console.WriteLine("Could not set service description; ignoring");
235129

236-
Console.WriteLine("Service installed successfully.");
237-
}
130+
Console.WriteLine("Service installed successfully.");
131+
}
238132

239-
void GiveFullControl(string target)
240-
{
241-
if (target == null) throw new ArgumentNullException(nameof(target));
133+
void GiveFullControl(string target)
134+
{
135+
if (target == null) throw new ArgumentNullException(nameof(target));
242136

243-
if (!Directory.Exists(target))
244-
Directory.CreateDirectory(target);
137+
if (!Directory.Exists(target))
138+
Directory.CreateDirectory(target);
245139

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-
}
140+
var storageInfo = new DirectoryInfo(target);
141+
var storageAccessControl = storageInfo.GetAccessControl();
142+
storageAccessControl.AddAccessRule(new FileSystemAccessRule(ServiceUsername,
143+
FileSystemRights.FullControl, AccessControlType.Allow));
144+
storageInfo.SetAccessControl(storageAccessControl);
145+
}
252146

253-
static string MakeListenUriReservationPattern(string uri)
254-
{
255-
var listenUri = uri.Replace("localhost", "+");
256-
if (!listenUri.EndsWith("/"))
257-
listenUri += "/";
258-
return listenUri;
259-
}
147+
static string MakeListenUriReservationPattern(string uri)
148+
{
149+
var listenUri = uri.Replace("localhost", "+");
150+
if (!listenUri.EndsWith("/"))
151+
listenUri += "/";
152+
return listenUri;
260153
}
261154
}
262155

0 commit comments

Comments
 (0)