|
17 | 17 | using System; |
18 | 18 | using System.Diagnostics.CodeAnalysis; |
19 | 19 | using System.IO; |
20 | | -using System.Runtime.InteropServices; |
21 | 20 | using System.Security.AccessControl; |
22 | | -using System.ServiceProcess; |
23 | 21 | using System.Threading.Tasks; |
24 | 22 | using SeqCli.Cli.Features; |
25 | 23 | using SeqCli.Config; |
26 | 24 | using SeqCli.Forwarder.Cli.Features; |
27 | 25 | using SeqCli.Forwarder.ServiceProcess; |
28 | 26 | using SeqCli.Forwarder.Util; |
29 | 27 |
|
| 28 | +namespace SeqCli.Cli.Commands.Forwarder; |
| 29 | + |
30 | 30 | // ReSharper disable once ClassNeverInstantiated.Global |
31 | 31 |
|
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 |
33 | 35 | { |
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() |
37 | 40 | { |
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 | + } |
55 | 45 |
|
56 | | - string ServiceUsername => _serviceCredentials.IsUsernameSpecified ? _serviceCredentials.Username : "NT AUTHORITY\\LocalService"; |
| 46 | + string ServiceUsername => _serviceCredentials.IsUsernameSpecified ? _serviceCredentials.Username : "NT AUTHORITY\\LocalService"; |
57 | 47 |
|
58 | | - protected override Task<int> Run() |
| 48 | + protected override Task<int> Run() |
| 49 | + { |
| 50 | + try |
59 | 51 | { |
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); |
87 | 54 | } |
88 | | - |
89 | | - int Setup() |
| 55 | + catch (DirectoryNotFoundException dex) |
90 | 56 | { |
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); |
110 | 59 | } |
111 | | - |
112 | | - static void Reconfigure(ServiceController controller) |
| 60 | + catch (Exception ex) |
113 | 61 | { |
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); |
146 | 64 | } |
| 65 | + } |
147 | 66 |
|
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..."); |
163 | 70 |
|
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); |
166 | 78 | } |
167 | 79 |
|
168 | | - [DllImport("shlwapi.dll")] |
169 | | - static extern bool PathIsNetworkPath(string pszPath); |
170 | | - |
171 | | - void Install() |
| 80 | + if (_serviceCredentials.IsUsernameSpecified) |
172 | 81 | { |
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."); |
197 | 85 |
|
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 | + } |
203 | 90 |
|
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); |
209 | 93 |
|
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); |
212 | 96 |
|
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"); |
214 | 102 |
|
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}\""; |
219 | 105 |
|
220 | | - if (_serviceCredentials.IsUsernameSpecified) |
221 | | - scCmdline += $" obj= {_serviceCredentials.Username} password= {_serviceCredentials.Password}"; |
| 106 | + var binPath = forwarderRunCmdline.Replace("\"", "\\\""); |
222 | 107 |
|
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"; |
228 | 112 |
|
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}"; |
235 | 115 |
|
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."); |
237 | 120 | } |
238 | 121 |
|
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"); |
242 | 128 |
|
243 | | - if (!Directory.Exists(target)) |
244 | | - Directory.CreateDirectory(target); |
| 129 | + Console.WriteLine("Service installed successfully."); |
| 130 | + } |
245 | 131 |
|
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 | + } |
252 | 143 |
|
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; |
260 | 150 | } |
261 | 151 | } |
262 | 152 |
|
|
0 commit comments