Standardize coding styles

pull/418/head
NextTurn 2020-07-08 00:00:00 +08:00 committed by Next Turn
parent 885beb89a2
commit 35af3bf78d
63 changed files with 894 additions and 712 deletions

View File

@ -1,8 +1,13 @@
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DebugType>full</DebugType>
<ArtifactsDir>$(MSBuildThisFileDirectory)artifacts\</ArtifactsDir>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)src\stylecop.json" />
</ItemGroup>
</Project>

70
src/.editorconfig Normal file
View File

@ -0,0 +1,70 @@
[*.cs]
# SA0001: XML comment analysis is disabled due to project configuration
dotnet_diagnostic.SA0001.severity = none
# SA1114: Parameter list should follow declaration
dotnet_diagnostic.SA1114.severity = none
# SA1124: Do not use regions
dotnet_diagnostic.SA1124.severity = none
# SA1201: Elements should appear in the correct order
dotnet_diagnostic.SA1201.severity = none
# SA1202: Elements should be ordered by access
dotnet_diagnostic.SA1202.severity = none
# SA1401: Fields should be private
dotnet_diagnostic.SA1401.severity = none
# SA1402: File may only contain a single type
dotnet_diagnostic.SA1402.severity = none
# SA1405: Debug.Assert should provide message text
dotnet_diagnostic.SA1405.severity = none
# SA1413: Use trailing comma in multi-line initializers
dotnet_diagnostic.SA1413.severity = none
# SA1600: Elements should be documented
dotnet_diagnostic.SA1600.severity = none
# SA1601: Partial elements should be documented
dotnet_diagnostic.SA1601.severity = none
# SA1602: Enumeration items should be documented
dotnet_diagnostic.SA1602.severity = none
# SA1604: Element documentation should have summary
dotnet_diagnostic.SA1604.severity = none
# SA1606: Element documentation should have summary text
dotnet_diagnostic.SA1606.severity = none
# SA1611: Element parameters should be documented
dotnet_diagnostic.SA1611.severity = none
# SA1615: Element return value should be documented
dotnet_diagnostic.SA1615.severity = none
# SA1618: Generic type parameters should be documented
dotnet_diagnostic.SA1618.severity = none
# SA1623: Property summary documentation should match accessors
dotnet_diagnostic.SA1623.severity = none
# SA1627: Documentation text should not be empty
dotnet_diagnostic.SA1627.severity = none
# SA1629: Documentation text should end with a period
dotnet_diagnostic.SA1629.severity = none
# SA1633: File should have header
dotnet_diagnostic.SA1633.severity = none
# SA1642: Constructor summary documentation should begin with standard text
dotnet_diagnostic.SA1642.severity = none
# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none

View File

@ -1,20 +1,20 @@
using System.Diagnostics;
namespace winsw.Logging
namespace WinSW.Logging
{
/// <summary>
/// Implements caching of the WindowsService reference in WinSW.
/// </summary>
public class WrapperServiceEventLogProvider : IServiceEventLogProvider
{
public WrapperService? service { get; set; }
public WrapperService? Service { get; set; }
public EventLog? locate()
public EventLog? Locate()
{
WrapperService? _service = service;
if (_service != null && !_service.IsShuttingDown)
WrapperService? service = this.Service;
if (service != null && !service.IsShuttingDown)
{
return _service.EventLog;
return service.EventLog;
}
// By default return null

View File

@ -16,10 +16,10 @@ using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using winsw.Logging;
using winsw.Native;
using WinSW.Logging;
using WinSW.Native;
namespace winsw
namespace WinSW
{
public static class Program
{
@ -456,7 +456,6 @@ namespace winsw
default:
throw;
}
}
}
@ -530,7 +529,6 @@ namespace winsw
default:
throw;
}
}
@ -723,7 +721,7 @@ namespace winsw
{
Name = "Wrapper event log",
Threshold = eventLogLevel,
provider = WrapperService.eventLogProvider,
Provider = WrapperService.eventLogProvider,
};
systemEventLogger.ActivateOptions();
appenders.Add(systemEventLogger);

View File

@ -2,7 +2,7 @@
using System.ServiceProcess;
using TimeoutException = System.ServiceProcess.TimeoutException;
namespace winsw
namespace WinSW
{
internal static class ServiceControllerExtension
{

View File

@ -1,6 +1,6 @@
using System;
namespace winsw
namespace WinSW
{
internal sealed class UserException : Exception
{

View File

@ -8,36 +8,37 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using log4net;
using winsw.Extensions;
using winsw.Logging;
using winsw.Native;
using winsw.Util;
using WinSW.Extensions;
using WinSW.Logging;
using WinSW.Native;
using WinSW.Util;
namespace winsw
namespace WinSW
{
public class WrapperService : ServiceBase, EventLogger
public class WrapperService : ServiceBase, IEventLogger
{
private ServiceApis.SERVICE_STATUS _wrapperServiceStatus;
private ServiceApis.SERVICE_STATUS wrapperServiceStatus;
private readonly Process _process = new Process();
private readonly ServiceDescriptor _descriptor;
private Dictionary<string, string>? _envs;
private readonly Process process = new Process();
private readonly ServiceDescriptor descriptor;
private Dictionary<string, string>? envs;
internal WinSWExtensionManager ExtensionManager { get; private set; }
internal WinSWExtensionManager ExtensionManager { get; }
private static readonly ILog Log = LogManager.GetLogger(
#if NETCOREAPP
Assembly.GetExecutingAssembly(),
#endif
"WinSW");
internal static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider();
/// <summary>
/// Indicates to the watch dog thread that we are going to terminate the process,
/// so don't try to kill us when the child exits.
/// </summary>
private bool _orderlyShutdown;
private bool _systemShuttingdown;
private bool orderlyShutdown;
private bool systemShuttingdown;
/// <summary>
/// Version of Windows service wrapper
@ -50,24 +51,25 @@ namespace winsw
/// <summary>
/// Indicates that the system is shutting down.
/// </summary>
public bool IsShuttingDown => _systemShuttingdown;
public bool IsShuttingDown => this.systemShuttingdown;
public WrapperService(ServiceDescriptor descriptor)
{
_descriptor = descriptor;
ServiceName = _descriptor.Id;
ExtensionManager = new WinSWExtensionManager(_descriptor);
CanShutdown = true;
CanStop = true;
CanPauseAndContinue = false;
AutoLog = true;
_systemShuttingdown = false;
this.descriptor = descriptor;
this.ServiceName = this.descriptor.Id;
this.ExtensionManager = new WinSWExtensionManager(this.descriptor);
this.CanShutdown = true;
this.CanStop = true;
this.CanPauseAndContinue = false;
this.AutoLog = true;
this.systemShuttingdown = false;
// Register the event log provider
eventLogProvider.service = this;
eventLogProvider.Service = this;
}
public WrapperService() : this(new ServiceDescriptor())
public WrapperService()
: this(new ServiceDescriptor())
{
}
@ -77,9 +79,11 @@ namespace winsw
/// </summary>
private void HandleFileCopies()
{
var file = _descriptor.BasePath + ".copies";
var file = this.descriptor.BasePath + ".copies";
if (!File.Exists(file))
{
return; // nothing to handle
}
try
{
@ -87,15 +91,15 @@ namespace winsw
string? line;
while ((line = tr.ReadLine()) != null)
{
LogEvent("Handling copy: " + line);
this.LogEvent("Handling copy: " + line);
string[] tokens = line.Split('>');
if (tokens.Length > 2)
{
LogEvent("Too many delimiters in " + line);
this.LogEvent("Too many delimiters in " + line);
continue;
}
MoveFile(tokens[0], tokens[1]);
this.MoveFile(tokens[0], tokens[1]);
}
}
finally
@ -115,7 +119,7 @@ namespace winsw
}
catch (IOException e)
{
LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message);
this.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message);
}
}
@ -125,21 +129,21 @@ namespace winsw
/// <returns>Log Handler, which should be used for the spawned process</returns>
private LogHandler CreateExecutableLogHandler()
{
string logDirectory = _descriptor.LogDirectory;
string logDirectory = this.descriptor.LogDirectory;
if (!Directory.Exists(logDirectory))
{
Directory.CreateDirectory(logDirectory);
}
LogHandler logAppender = _descriptor.LogHandler;
LogHandler logAppender = this.descriptor.LogHandler;
logAppender.EventLogger = this;
return logAppender;
}
public void LogEvent(string message)
{
if (_systemShuttingdown)
if (this.systemShuttingdown)
{
/* NOP - cannot call EventLog because of shutdown. */
}
@ -147,7 +151,7 @@ namespace winsw
{
try
{
EventLog.WriteEntry(message);
this.EventLog.WriteEntry(message);
}
catch (Exception e)
{
@ -158,7 +162,7 @@ namespace winsw
public void LogEvent(string message, EventLogEntryType type)
{
if (_systemShuttingdown)
if (this.systemShuttingdown)
{
/* NOP - cannot call EventLog because of shutdown. */
}
@ -166,7 +170,7 @@ namespace winsw
{
try
{
EventLog.WriteEntry(message, type);
this.EventLog.WriteEntry(message, type);
}
catch (Exception e)
{
@ -177,24 +181,18 @@ namespace winsw
protected override void OnStart(string[] args)
{
_envs = _descriptor.EnvironmentVariables;
// TODO: Disabled according to security concerns in https://github.com/kohsuke/winsw/issues/54
// Could be restored, but unlikely it's required in event logs at all
/**
foreach (string key in _envs.Keys)
{
LogEvent("envar " + key + '=' + _envs[key]);
}*/
HandleFileCopies();
this.envs = this.descriptor.EnvironmentVariables;
this.HandleFileCopies();
// handle downloads
List<Download> downloads = _descriptor.Downloads;
List<Download> downloads = this.descriptor.Downloads;
Task[] tasks = new Task[downloads.Count];
for (int i = 0; i < downloads.Count; i++)
{
Download download = downloads[i];
string downloadMessage = $"Downloading: {download.From} to {download.To}. failOnError={download.FailOnError.ToString()}";
LogEvent(downloadMessage);
this.LogEvent(downloadMessage);
Log.Info(downloadMessage);
tasks[i] = download.PerformAsync();
}
@ -213,7 +211,7 @@ namespace winsw
Download download = downloads[i];
string errorMessage = $"Failed to download {download.From} to {download.To}";
AggregateException exception = tasks[i].Exception!;
LogEvent($"{errorMessage}. {exception.Message}");
this.LogEvent($"{errorMessage}. {exception.Message}");
Log.Error(errorMessage, exception);
// TODO: move this code into the download logic
@ -227,34 +225,34 @@ namespace winsw
throw new AggregateException(exceptions);
}
string? startArguments = _descriptor.StartArguments;
string? startArguments = this.descriptor.StartArguments;
if (startArguments is null)
{
startArguments = _descriptor.Arguments;
startArguments = this.descriptor.Arguments;
}
else
{
startArguments += " " + _descriptor.Arguments;
startArguments += " " + this.descriptor.Arguments;
}
// Converting newlines, line returns, tabs into a single
// Converting newlines, line returns, tabs into a single
// space. This allows users to provide multi-line arguments
// in the xml for readability.
startArguments = Regex.Replace(startArguments, @"\s*[\n\r]+\s*", " ");
LogEvent("Starting " + _descriptor.Executable + ' ' + startArguments);
Log.Info("Starting " + _descriptor.Executable + ' ' + startArguments);
this.LogEvent("Starting " + this.descriptor.Executable + ' ' + startArguments);
Log.Info("Starting " + this.descriptor.Executable + ' ' + startArguments);
// Load and start extensions
ExtensionManager.LoadExtensions();
ExtensionManager.FireOnWrapperStarted();
this.ExtensionManager.LoadExtensions();
this.ExtensionManager.FireOnWrapperStarted();
LogHandler executableLogHandler = CreateExecutableLogHandler();
StartProcess(_process, startArguments, _descriptor.Executable, executableLogHandler, true);
ExtensionManager.FireOnProcessStarted(_process);
LogHandler executableLogHandler = this.CreateExecutableLogHandler();
this.StartProcess(this.process, startArguments, this.descriptor.Executable, executableLogHandler, true);
this.ExtensionManager.FireOnProcessStarted(this.process);
_process.StandardInput.Close(); // nothing for you to read!
this.process.StandardInput.Close(); // nothing for you to read!
}
protected override void OnShutdown()
@ -263,8 +261,8 @@ namespace winsw
try
{
_systemShuttingdown = true;
StopIt();
this.systemShuttingdown = true;
this.StopIt();
}
catch (Exception ex)
{
@ -278,7 +276,7 @@ namespace winsw
try
{
StopIt();
this.StopIt();
}
catch (Exception ex)
{
@ -295,18 +293,18 @@ namespace winsw
/// </summary>
private void StopIt()
{
string? stopArguments = _descriptor.StopArguments;
LogEvent("Stopping " + _descriptor.Id);
Log.Info("Stopping " + _descriptor.Id);
_orderlyShutdown = true;
string? stopArguments = this.descriptor.StopArguments;
this.LogEvent("Stopping " + this.descriptor.Id);
Log.Info("Stopping " + this.descriptor.Id);
this.orderlyShutdown = true;
if (stopArguments is null)
{
try
{
Log.Debug("ProcessKill " + _process.Id);
ProcessHelper.StopProcessTree(_process, _descriptor.StopTimeout);
ExtensionManager.FireOnProcessTerminated(_process);
Log.Debug("ProcessKill " + this.process.Id);
ProcessHelper.StopProcessTree(this.process, this.descriptor.StopTimeout);
this.ExtensionManager.FireOnProcessTerminated(this.process);
}
catch (InvalidOperationException)
{
@ -315,48 +313,48 @@ namespace winsw
}
else
{
SignalShutdownPending();
this.SignalShutdownPending();
stopArguments += " " + _descriptor.Arguments;
stopArguments += " " + this.descriptor.Arguments;
Process stopProcess = new Process();
string? executable = _descriptor.StopExecutable;
string? executable = this.descriptor.StopExecutable;
executable ??= _descriptor.Executable;
executable ??= this.descriptor.Executable;
// TODO: Redirect logging to Log4Net once https://github.com/kohsuke/winsw/pull/213 is integrated
StartProcess(stopProcess, stopArguments, executable, null, false);
this.StartProcess(stopProcess, stopArguments, executable, null, false);
Log.Debug("WaitForProcessToExit " + _process.Id + "+" + stopProcess.Id);
WaitForProcessToExit(_process);
WaitForProcessToExit(stopProcess);
Log.Debug("WaitForProcessToExit " + this.process.Id + "+" + stopProcess.Id);
this.WaitForProcessToExit(this.process);
this.WaitForProcessToExit(stopProcess);
}
// Stop extensions
ExtensionManager.FireBeforeWrapperStopped();
this.ExtensionManager.FireBeforeWrapperStopped();
if (_systemShuttingdown && _descriptor.BeepOnShutdown)
if (this.systemShuttingdown && this.descriptor.BeepOnShutdown)
{
Console.Beep();
}
Log.Info("Finished " + _descriptor.Id);
Log.Info("Finished " + this.descriptor.Id);
}
private void WaitForProcessToExit(Process processoWait)
{
SignalShutdownPending();
this.SignalShutdownPending();
int effectiveProcessWaitSleepTime;
if (_descriptor.SleepTime.TotalMilliseconds > int.MaxValue)
if (this.descriptor.SleepTime.TotalMilliseconds > int.MaxValue)
{
Log.Warn("The requested sleep time " + _descriptor.SleepTime.TotalMilliseconds + "is greater that the max value " +
Log.Warn("The requested sleep time " + this.descriptor.SleepTime.TotalMilliseconds + "is greater that the max value " +
int.MaxValue + ". The value will be truncated");
effectiveProcessWaitSleepTime = int.MaxValue;
}
else
{
effectiveProcessWaitSleepTime = (int)_descriptor.SleepTime.TotalMilliseconds;
effectiveProcessWaitSleepTime = (int)this.descriptor.SleepTime.TotalMilliseconds;
}
try
@ -365,7 +363,7 @@ namespace winsw
while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
{
SignalShutdownPending();
this.SignalShutdownPending();
// WriteEvent("WaitForProcessToExit [repeat]");
}
}
@ -380,27 +378,27 @@ namespace winsw
private void SignalShutdownPending()
{
int effectiveWaitHint;
if (_descriptor.WaitHint.TotalMilliseconds > int.MaxValue)
if (this.descriptor.WaitHint.TotalMilliseconds > int.MaxValue)
{
Log.Warn("The requested WaitHint value (" + _descriptor.WaitHint.TotalMilliseconds + " ms) is greater that the max value " +
Log.Warn("The requested WaitHint value (" + this.descriptor.WaitHint.TotalMilliseconds + " ms) is greater that the max value " +
int.MaxValue + ". The value will be truncated");
effectiveWaitHint = int.MaxValue;
}
else
{
effectiveWaitHint = (int)_descriptor.WaitHint.TotalMilliseconds;
effectiveWaitHint = (int)this.descriptor.WaitHint.TotalMilliseconds;
}
RequestAdditionalTime(effectiveWaitHint);
this.RequestAdditionalTime(effectiveWaitHint);
}
private void SignalShutdownComplete()
{
IntPtr handle = ServiceHandle;
_wrapperServiceStatus.CheckPoint++;
IntPtr handle = this.ServiceHandle;
this.wrapperServiceStatus.CheckPoint++;
// WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
_wrapperServiceStatus.CurrentState = ServiceControllerStatus.Stopped;
ServiceApis.SetServiceStatus(handle, _wrapperServiceStatus);
this.wrapperServiceStatus.CurrentState = ServiceControllerStatus.Stopped;
ServiceApis.SetServiceStatus(handle, this.wrapperServiceStatus);
}
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler, bool redirectStdin)
@ -411,25 +409,28 @@ namespace winsw
string msg = processToStart.Id + " - " + processToStart.StartInfo.FileName + " " + processToStart.StartInfo.Arguments;
try
{
if (_orderlyShutdown)
if (this.orderlyShutdown)
{
LogEvent("Child process [" + msg + "] terminated with " + proc.ExitCode, EventLogEntryType.Information);
this.LogEvent("Child process [" + msg + "] terminated with " + proc.ExitCode, EventLogEntryType.Information);
}
else
{
LogEvent("Child process [" + msg + "] finished with " + proc.ExitCode, EventLogEntryType.Warning);
this.LogEvent("Child process [" + msg + "] finished with " + proc.ExitCode, EventLogEntryType.Warning);
// if we finished orderly, report that to SCM.
// by not reporting unclean shutdown, we let Windows SCM to decide if it wants to
// restart the service automatically
if (proc.ExitCode == 0)
SignalShutdownComplete();
{
this.SignalShutdownComplete();
}
Environment.Exit(proc.ExitCode);
}
}
catch (InvalidOperationException ioe)
{
LogEvent("WaitForExit " + ioe.Message);
this.LogEvent("WaitForExit " + ioe.Message);
}
finally
{
@ -442,13 +443,13 @@ namespace winsw
processToStart: processToStart,
executable: executable,
arguments: arguments,
envVars: _envs,
workingDirectory: _descriptor.WorkingDirectory,
priority: _descriptor.Priority,
envVars: this.envs,
workingDirectory: this.descriptor.WorkingDirectory,
priority: this.descriptor.Priority,
callback: OnProcessCompleted,
logHandler: logHandler,
redirectStdin: redirectStdin,
hideWindow: _descriptor.HideWindow);
hideWindow: this.descriptor.HideWindow);
}
}
}

View File

@ -5,56 +5,80 @@ using System.IO;
using System.ServiceProcess;
using System.Xml;
namespace winsw.Configuration
namespace WinSW.Configuration
{
/// <summary>
/// Default WinSW settings
/// </summary>
public sealed class DefaultWinSWSettings : IWinSWConfiguration
{
public string Id => throw new InvalidOperationException(nameof(Id) + " must be specified.");
public string Caption => throw new InvalidOperationException(nameof(Caption) + " must be specified.");
public string Description => throw new InvalidOperationException(nameof(Description) + " must be specified.");
public string Executable => throw new InvalidOperationException(nameof(Executable) + " must be specified.");
public string Id => throw new InvalidOperationException(nameof(this.Id) + " must be specified.");
public string Caption => throw new InvalidOperationException(nameof(this.Caption) + " must be specified.");
public string Description => throw new InvalidOperationException(nameof(this.Description) + " must be specified.");
public string Executable => throw new InvalidOperationException(nameof(this.Executable) + " must be specified.");
public bool HideWindow => false;
public string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName;
// Installation
public bool AllowServiceAcountLogonRight => false;
public string? ServiceAccountPassword => null;
public string? ServiceAccountUserName => null;
public Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0];
public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1);
// Executable management
public string Arguments => string.Empty;
public string? StartArguments => null;
public string? StopExecutable => null;
public string? StopArguments => null;
public string WorkingDirectory => Path.GetDirectoryName(ExecutablePath)!;
public string WorkingDirectory => Path.GetDirectoryName(this.ExecutablePath)!;
public ProcessPriorityClass Priority => ProcessPriorityClass.Normal;
public TimeSpan StopTimeout => TimeSpan.FromSeconds(15);
// Service management
public ServiceStartMode StartMode => ServiceStartMode.Automatic;
public bool DelayedAutoStart => false;
public string[] ServiceDependencies => new string[0];
public TimeSpan WaitHint => TimeSpan.FromSeconds(15);
public TimeSpan SleepTime => TimeSpan.FromSeconds(1);
public bool Interactive => false;
// Logging
public string LogDirectory => Path.GetDirectoryName(ExecutablePath)!;
public string LogDirectory => Path.GetDirectoryName(this.ExecutablePath)!;
public string LogMode => "append";
public bool OutFileDisabled => false;
public bool ErrFileDisabled => false;
public string OutFilePattern => ".out.log";
public string ErrFilePattern => ".err.log";
// Environment
public List<Download> Downloads => new List<Download>(0);
public Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(0);
// Misc

View File

@ -4,49 +4,69 @@ using System.Diagnostics;
using System.ServiceProcess;
using System.Xml;
namespace winsw.Configuration
namespace WinSW.Configuration
{
// TODO: Document the parameters && refactor
public interface IWinSWConfiguration
{
// TODO: Document the parameters && refactor
string Id { get; }
string Caption { get; }
string Description { get; }
string Executable { get; }
string ExecutablePath { get; }
bool HideWindow { get; }
// Installation
bool AllowServiceAcountLogonRight { get; }
string? ServiceAccountPassword { get; }
string? ServiceAccountUserName { get; }
Native.SC_ACTION[] FailureActions { get; }
TimeSpan ResetFailureAfter { get; }
// Executable management
string Arguments { get; }
string? StartArguments { get; }
string? StopExecutable { get; }
string? StopArguments { get; }
string WorkingDirectory { get; }
ProcessPriorityClass Priority { get; }
TimeSpan StopTimeout { get; }
// Service management
ServiceStartMode StartMode { get; }
string[] ServiceDependencies { get; }
TimeSpan WaitHint { get; }
TimeSpan SleepTime { get; }
bool Interactive { get; }
// Logging
string LogDirectory { get; }
// TODO: replace by enum
string LogMode { get; }
// Environment
List<Download> Downloads { get; }
Dictionary<string, string> EnvironmentVariables { get; }
// Misc

View File

@ -5,9 +5,9 @@ using System.Text;
using System.Threading.Tasks;
using System.Xml;
using log4net;
using winsw.Util;
using WinSW.Util;
namespace winsw
namespace WinSW
{
/// <summary>
/// Specify the download activities prior to the launch.
@ -17,9 +17,9 @@ namespace winsw
{
public enum AuthType
{
none = 0,
sspi,
basic
None = 0,
Sspi,
Basic
}
private static readonly ILog Logger = LogManager.GetLogger(typeof(Download));
@ -33,7 +33,7 @@ namespace winsw
public readonly bool FailOnError;
public readonly string? Proxy;
public string ShortId => $"(download from {From})";
public string ShortId => $"(download from {this.From})";
#if NET461
static Download()
@ -48,20 +48,20 @@ namespace winsw
string from,
string to,
bool failOnError = false,
AuthType auth = AuthType.none,
AuthType auth = AuthType.None,
string? username = null,
string? password = null,
bool unsecureAuth = false,
string? proxy = null)
{
From = from;
To = to;
FailOnError = failOnError;
Proxy = proxy;
Auth = auth;
Username = username;
Password = password;
UnsecureAuth = unsecureAuth;
this.From = from;
this.To = to;
this.FailOnError = failOnError;
this.Proxy = proxy;
this.Auth = auth;
this.Username = username;
this.Password = password;
this.UnsecureAuth = unsecureAuth;
}
/// <summary>
@ -71,36 +71,36 @@ namespace winsw
/// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid</exception>
internal Download(XmlElement n)
{
From = XmlHelper.SingleAttribute<string>(n, "from");
To = XmlHelper.SingleAttribute<string>(n, "to");
this.From = XmlHelper.SingleAttribute<string>(n, "from");
this.To = XmlHelper.SingleAttribute<string>(n, "to");
// All arguments below are optional
FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false);
Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null);
this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false);
this.Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null);
Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.none);
Username = XmlHelper.SingleAttribute<string>(n, "user", null);
Password = XmlHelper.SingleAttribute<string>(n, "password", null);
UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false);
this.Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.None);
this.Username = XmlHelper.SingleAttribute<string>(n, "user", null);
this.Password = XmlHelper.SingleAttribute<string>(n, "password", null);
this.UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false);
if (Auth == AuthType.basic)
if (this.Auth == AuthType.Basic)
{
// Allow it only for HTTPS or for UnsecureAuth
if (!From.StartsWith("https:") && !UnsecureAuth)
if (!this.From.StartsWith("https:") && !this.UnsecureAuth)
{
throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + ShortId +
throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + this.ShortId +
"If you really want this you must enable 'unsecureAuth' in the configuration");
}
// Also fail if there is no user/password
if (Username is null)
if (this.Username is null)
{
throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + ShortId);
throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId);
}
if (Password is null)
if (this.Password is null)
{
throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + ShortId);
throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId);
}
}
}
@ -121,10 +121,10 @@ namespace winsw
/// </exception>
public async Task PerformAsync()
{
WebRequest request = WebRequest.Create(From);
if (!string.IsNullOrEmpty(Proxy))
WebRequest request = WebRequest.Create(this.From);
if (!string.IsNullOrEmpty(this.Proxy))
{
CustomProxyInformation proxyInformation = new CustomProxyInformation(Proxy);
CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!);
if (proxyInformation.Credentials != null)
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials);
@ -135,35 +135,35 @@ namespace winsw
}
}
switch (Auth)
switch (this.Auth)
{
case AuthType.none:
case AuthType.None:
// Do nothing
break;
case AuthType.sspi:
case AuthType.Sspi:
request.UseDefaultCredentials = true;
request.PreAuthenticate = true;
request.Credentials = CredentialCache.DefaultCredentials;
break;
case AuthType.basic:
SetBasicAuthHeader(request, Username!, Password!);
case AuthType.Basic:
this.SetBasicAuthHeader(request, this.Username!, this.Password!);
break;
default:
throw new WebException("Code defect. Unsupported authentication type: " + Auth);
throw new WebException("Code defect. Unsupported authentication type: " + this.Auth);
}
bool supportsIfModifiedSince = false;
if (request is HttpWebRequest httpRequest && File.Exists(To))
if (request is HttpWebRequest httpRequest && File.Exists(this.To))
{
supportsIfModifiedSince = true;
httpRequest.IfModifiedSince = File.GetLastWriteTime(To);
httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To);
}
DateTime lastModified = default;
string tmpFilePath = To + ".tmp";
string tmpFilePath = this.To + ".tmp";
try
{
using (WebResponse response = await request.GetResponseAsync())
@ -178,18 +178,18 @@ namespace winsw
await responseStream.CopyToAsync(tmpStream);
}
FileHelper.MoveOrReplaceFile(To + ".tmp", To);
FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To);
if (supportsIfModifiedSince)
{
File.SetLastWriteTime(To, lastModified);
File.SetLastWriteTime(this.To, lastModified);
}
}
catch (WebException e)
{
if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified)
{
Logger.Info($"Skipped downloading unmodified resource '{From}'");
Logger.Info($"Skipped downloading unmodified resource '{this.From}'");
}
else
{
@ -201,8 +201,9 @@ namespace winsw
public class CustomProxyInformation
{
public string ServerAddress { get; set; }
public NetworkCredential? Credentials { get; set; }
public string ServerAddress { get; }
public NetworkCredential? Credentials { get; }
public CustomProxyInformation(string proxy)
{
@ -216,12 +217,12 @@ namespace winsw
string username = completeCredsStr.Substring(0, credsSeparator);
string password = completeCredsStr.Substring(credsSeparator + 1);
Credentials = new NetworkCredential(username, password);
ServerAddress = proxy.Replace(completeCredsStr + "@", "");
this.Credentials = new NetworkCredential(username, password);
this.ServerAddress = proxy.Replace(completeCredsStr + "@", null);
}
else
{
ServerAddress = proxy;
this.ServerAddress = proxy;
}
}
}

View File

@ -1,6 +1,6 @@
using System.Xml;
namespace winsw.Extensions
namespace WinSW.Extensions
{
public abstract class AbstractWinSWExtension : IWinSWExtension
{

View File

@ -1,23 +1,23 @@
using System;
namespace winsw.Extensions
namespace WinSW.Extensions
{
public class ExtensionException : WinSWException
{
public string ExtensionId { get; private set; }
public string ExtensionId { get; }
public ExtensionException(string extensionName, string message)
: base(message)
{
ExtensionId = extensionName;
this.ExtensionId = extensionName;
}
public ExtensionException(string extensionName, string message, Exception innerException)
: base(message, innerException)
{
ExtensionId = extensionName;
this.ExtensionId = extensionName;
}
public override string Message => ExtensionId + ": " + base.Message;
public override string Message => this.ExtensionId + ": " + base.Message;
}
}

View File

@ -1,12 +0,0 @@
namespace winsw.Extensions
{
/// <summary>
/// This attribute is used to identify extension points within the code
/// </summary>
/// <remarks>
/// Each extension point implements its own entry type.
/// </remarks>
class ExtensionPointAttribute
{
}
}

View File

@ -1,6 +1,6 @@
using System.Xml;
namespace winsw.Extensions
namespace WinSW.Extensions
{
/// <summary>
/// Interface for Win Service Wrapper Extension

View File

@ -1,7 +1,7 @@
using System.Xml;
using winsw.Util;
using WinSW.Util;
namespace winsw.Extensions
namespace WinSW.Extensions
{
/// <summary>
/// Describes WinSW extensions in <see cref="IWinSWExtension"/>
@ -14,23 +14,23 @@ namespace winsw.Extensions
/// <summary>
/// Unique extension ID
/// </summary>
public string Id { get; private set; }
public string Id { get; }
/// <summary>
/// Exception is enabled
/// </summary>
public bool Enabled { get; private set; }
public bool Enabled { get; }
/// <summary>
/// Extension classname
/// </summary>
public string ClassName { get; private set; }
public string ClassName { get; }
private WinSWExtensionDescriptor(string id, string className, bool enabled)
{
Id = id;
Enabled = enabled;
ClassName = className;
this.Id = id;
this.Enabled = enabled;
this.ClassName = className;
}
public static WinSWExtensionDescriptor FromXml(XmlElement node)

View File

@ -3,20 +3,20 @@ using System.Collections.Generic;
using System.Xml;
using log4net;
namespace winsw.Extensions
namespace WinSW.Extensions
{
public class WinSWExtensionManager
{
public Dictionary<string, IWinSWExtension> Extensions { get; private set; }
public Dictionary<string, IWinSWExtension> Extensions { get; }
public ServiceDescriptor ServiceDescriptor { get; private set; }
public ServiceDescriptor ServiceDescriptor { get; }
private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager));
public WinSWExtensionManager(ServiceDescriptor serviceDescriptor)
{
ServiceDescriptor = serviceDescriptor;
Extensions = new Dictionary<string, IWinSWExtension>();
this.ServiceDescriptor = serviceDescriptor;
this.Extensions = new Dictionary<string, IWinSWExtension>();
}
/// <summary>
@ -27,7 +27,7 @@ namespace winsw.Extensions
/// <exception cref="Exception">Start failure</exception>
public void FireOnWrapperStarted()
{
foreach (var ext in Extensions)
foreach (var ext in this.Extensions)
{
try
{
@ -47,7 +47,7 @@ namespace winsw.Extensions
/// </summary>
public void FireBeforeWrapperStopped()
{
foreach (var ext in Extensions)
foreach (var ext in this.Extensions)
{
try
{
@ -66,7 +66,7 @@ namespace winsw.Extensions
/// <param name="process">Process</param>
public void FireOnProcessStarted(System.Diagnostics.Process process)
{
foreach (var ext in Extensions)
foreach (var ext in this.Extensions)
{
try
{
@ -85,7 +85,7 @@ namespace winsw.Extensions
/// <param name="process">Process</param>
public void FireOnProcessTerminated(System.Diagnostics.Process process)
{
foreach (var ext in Extensions)
foreach (var ext in this.Extensions)
{
try
{
@ -107,10 +107,10 @@ namespace winsw.Extensions
/// <exception cref="Exception">Loading failure</exception>
public void LoadExtensions()
{
var extensionIds = ServiceDescriptor.ExtensionIds;
var extensionIds = this.ServiceDescriptor.ExtensionIds;
foreach (string extensionId in extensionIds)
{
LoadExtension(extensionId);
this.LoadExtension(extensionId);
}
}
@ -122,12 +122,12 @@ namespace winsw.Extensions
/// <exception cref="Exception">Loading failure</exception>
private void LoadExtension(string id)
{
if (Extensions.ContainsKey(id))
if (this.Extensions.ContainsKey(id))
{
throw new ExtensionException(id, "Extension has been already loaded");
}
XmlNode? extensionsConfig = ServiceDescriptor.ExtensionsConfiguration;
XmlNode? extensionsConfig = this.ServiceDescriptor.ExtensionsConfiguration;
XmlElement? configNode = extensionsConfig is null ? null : extensionsConfig.SelectSingleNode("extension[@id='" + id + "'][1]") as XmlElement;
if (configNode is null)
{
@ -137,11 +137,11 @@ namespace winsw.Extensions
var descriptor = WinSWExtensionDescriptor.FromXml(configNode);
if (descriptor.Enabled)
{
IWinSWExtension extension = CreateExtensionInstance(descriptor.Id, descriptor.ClassName);
IWinSWExtension extension = this.CreateExtensionInstance(descriptor.Id, descriptor.ClassName);
extension.Descriptor = descriptor;
try
{
extension.Configure(ServiceDescriptor, configNode);
extension.Configure(this.ServiceDescriptor, configNode);
}
catch (Exception ex)
{ // Consider any unexpected exception as fatal
@ -149,7 +149,7 @@ namespace winsw.Extensions
throw ex;
}
Extensions.Add(id, extension);
this.Extensions.Add(id, extension);
Log.Info("Extension loaded: " + id);
}
else

View File

@ -1,14 +1,13 @@
using System;
using System.Diagnostics;
using System.IO.Compression;
using System.IO;
using System.IO.Compression;
using System.Threading;
using winsw.Util;
using WinSW.Util;
namespace winsw
namespace WinSW
{
// ReSharper disable once InconsistentNaming
public interface EventLogger
public interface IEventLogger
{
void LogEvent(string message);
@ -20,14 +19,13 @@ namespace winsw
/// </summary>
public abstract class LogHandler
{
// ReSharper disable once InconsistentNaming
public abstract void log(StreamReader outputReader, StreamReader errorReader);
public abstract void Log(StreamReader outputReader, StreamReader errorReader);
/// <summary>
/// Error and information about logging should be reported here.
/// </summary>
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public EventLogger EventLogger { get; set; }
public IEventLogger EventLogger { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
/// <summary>
@ -56,7 +54,7 @@ namespace winsw
}
catch (IOException e)
{
EventLogger.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message);
this.EventLogger.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message);
}
}
}
@ -66,22 +64,26 @@ namespace winsw
/// </summary>
public abstract class AbstractFileLogAppender : LogHandler
{
protected string BaseLogFileName { get; private set; }
protected bool OutFileDisabled { get; private set; }
protected bool ErrFileDisabled { get; private set; }
protected string OutFilePattern { get; private set; }
protected string ErrFilePattern { get; private set; }
protected string BaseLogFileName { get; }
protected bool OutFileDisabled { get; }
protected bool ErrFileDisabled { get; }
protected string OutFilePattern { get; }
protected string ErrFilePattern { get; }
protected AbstractFileLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
{
BaseLogFileName = Path.Combine(logDirectory, baseName);
OutFileDisabled = outFileDisabled;
OutFilePattern = outFilePattern;
ErrFileDisabled = errFileDisabled;
ErrFilePattern = errFilePattern;
this.BaseLogFileName = Path.Combine(logDirectory, baseName);
this.OutFileDisabled = outFileDisabled;
this.OutFilePattern = outFilePattern;
this.ErrFileDisabled = errFileDisabled;
this.ErrFilePattern = errFilePattern;
}
public override void log(StreamReader outputReader, StreamReader errorReader)
public override void Log(StreamReader outputReader, StreamReader errorReader)
{
if (this.OutFileDisabled)
{
@ -111,26 +113,28 @@ namespace winsw
public abstract class SimpleLogAppender : AbstractFileLogAppender
{
public FileMode FileMode { get; private set; }
public string OutputLogFileName { get; private set; }
public string ErrorLogFileName { get; private set; }
public FileMode FileMode { get; }
public string OutputLogFileName { get; }
public string ErrorLogFileName { get; }
protected SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{
FileMode = fileMode;
OutputLogFileName = BaseLogFileName + ".out.log";
ErrorLogFileName = BaseLogFileName + ".err.log";
this.FileMode = fileMode;
this.OutputLogFileName = this.BaseLogFileName + ".out.log";
this.ErrorLogFileName = this.BaseLogFileName + ".err.log";
}
protected override void LogOutput(StreamReader outputReader)
{
new Thread(() => CopyStream(outputReader, CreateWriter(new FileStream(OutputLogFileName, FileMode)))).Start();
new Thread(() => this.CopyStream(outputReader, this.CreateWriter(new FileStream(this.OutputLogFileName, this.FileMode)))).Start();
}
protected override void LogError(StreamReader errorReader)
{
new Thread(() => CopyStream(errorReader, CreateWriter(new FileStream(ErrorLogFileName, FileMode)))).Start();
new Thread(() => this.CopyStream(errorReader, this.CreateWriter(new FileStream(this.ErrorLogFileName, this.FileMode)))).Start();
}
}
@ -155,7 +159,7 @@ namespace winsw
/// </summary>
public class IgnoreLogAppender : LogHandler
{
public override void log(StreamReader outputReader, StreamReader errorReader)
public override void Log(StreamReader outputReader, StreamReader errorReader)
{
outputReader.Dispose();
errorReader.Dispose();
@ -164,24 +168,25 @@ namespace winsw
public class TimeBasedRollingLogAppender : AbstractFileLogAppender
{
public string Pattern { get; private set; }
public int Period { get; private set; }
public string Pattern { get; }
public int Period { get; }
public TimeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, string pattern, int period)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{
Pattern = pattern;
Period = period;
this.Pattern = pattern;
this.Period = period;
}
protected override void LogOutput(StreamReader outputReader)
{
new Thread(() => CopyStreamWithDateRotation(outputReader, OutFilePattern)).Start();
new Thread(() => this.CopyStreamWithDateRotation(outputReader, this.OutFilePattern)).Start();
}
protected override void LogError(StreamReader errorReader)
{
new Thread(() => CopyStreamWithDateRotation(errorReader, ErrFilePattern)).Start();
new Thread(() => this.CopyStreamWithDateRotation(errorReader, this.ErrFilePattern)).Start();
}
/// <summary>
@ -189,17 +194,17 @@ namespace winsw
/// </summary>
private void CopyStreamWithDateRotation(StreamReader reader, string ext)
{
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(Pattern, Period);
periodicRollingCalendar.init();
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.Pattern, this.Period);
periodicRollingCalendar.Init();
StreamWriter writer = CreateWriter(new FileStream(BaseLogFileName + "_" + periodicRollingCalendar.format + ext, FileMode.Append));
StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Append));
string? line;
while ((line = reader.ReadLine()) != null)
{
if (periodicRollingCalendar.shouldRoll)
if (periodicRollingCalendar.ShouldRoll)
{
writer.Dispose();
writer = CreateWriter(new FileStream(BaseLogFileName + "_" + periodicRollingCalendar.format + ext, FileMode.Create));
writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Create));
}
writer.WriteLine(line);
@ -212,37 +217,35 @@ namespace winsw
public class SizeBasedRollingLogAppender : AbstractFileLogAppender
{
// ReSharper disable once InconsistentNaming
public static int BYTES_PER_KB = 1024;
// ReSharper disable once InconsistentNaming
public static int BYTES_PER_MB = 1024 * BYTES_PER_KB;
// ReSharper disable once InconsistentNaming
public static int DEFAULT_SIZE_THRESHOLD = 10 * BYTES_PER_MB; // roll every 10MB.
// ReSharper disable once InconsistentNaming
public static int DEFAULT_FILES_TO_KEEP = 8;
public const int BytesPerKB = 1024;
public const int BytesPerMB = 1024 * BytesPerKB;
public const int DefaultSizeThreshold = 10 * BytesPerMB; // roll every 10MB.
public const int DefaultFilesToKeep = 8;
public int SizeTheshold { get; private set; }
public int SizeThreshold { get; }
public int FilesToKeep { get; private set; }
public int FilesToKeep { get; }
public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, int sizeThreshold, int filesToKeep)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{
SizeTheshold = sizeThreshold;
FilesToKeep = filesToKeep;
this.SizeThreshold = sizeThreshold;
this.FilesToKeep = filesToKeep;
}
public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
: this(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern, DEFAULT_SIZE_THRESHOLD, DEFAULT_FILES_TO_KEEP) { }
: this(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern, DefaultSizeThreshold, DefaultFilesToKeep)
{
}
protected override void LogOutput(StreamReader outputReader)
{
new Thread(() => CopyStreamWithRotation(outputReader, OutFilePattern)).Start();
new Thread(() => this.CopyStreamWithRotation(outputReader, this.OutFilePattern)).Start();
}
protected override void LogError(StreamReader errorReader)
{
new Thread(() => CopyStreamWithRotation(errorReader, ErrFilePattern)).Start();
new Thread(() => this.CopyStreamWithRotation(errorReader, this.ErrFilePattern)).Start();
}
/// <summary>
@ -250,41 +253,45 @@ namespace winsw
/// </summary>
private void CopyStreamWithRotation(StreamReader reader, string ext)
{
StreamWriter writer = CreateWriter(new FileStream(BaseLogFileName + ext, FileMode.Append));
long fileLength = new FileInfo(BaseLogFileName + ext).Length;
StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Append));
long fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
string? line;
while ((line = reader.ReadLine()) != null)
{
int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char);
if (fileLength + lengthToWrite > SizeTheshold)
if (fileLength + lengthToWrite > this.SizeThreshold)
{
writer.Dispose();
try
{
for (int j = FilesToKeep; j >= 1; j--)
for (int j = this.FilesToKeep; j >= 1; j--)
{
string dst = BaseLogFileName + "." + (j - 1) + ext;
string src = BaseLogFileName + "." + (j - 2) + ext;
string dst = this.BaseLogFileName + "." + (j - 1) + ext;
string src = this.BaseLogFileName + "." + (j - 2) + ext;
if (File.Exists(dst))
{
File.Delete(dst);
}
if (File.Exists(src))
{
File.Move(src, dst);
}
}
File.Move(BaseLogFileName + ext, BaseLogFileName + ".0" + ext);
File.Move(this.BaseLogFileName + ext, this.BaseLogFileName + ".0" + ext);
}
catch (IOException e)
{
EventLogger.LogEvent("Failed to roll log: " + e.Message);
this.EventLogger.LogEvent("Failed to roll log: " + e.Message);
}
// even if the log rotation fails, create a new one, or else
// we'll infinitely try to roll.
writer = CreateWriter(new FileStream(BaseLogFileName + ext, FileMode.Create));
fileLength = new FileInfo(BaseLogFileName + ext).Length;
writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Create));
fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
}
writer.WriteLine(line);
@ -306,26 +313,35 @@ namespace winsw
{
}
public override void log(StreamReader outputReader, StreamReader errorReader)
public override void Log(StreamReader outputReader, StreamReader errorReader)
{
if (!OutFileDisabled)
MoveFile(OutputLogFileName, OutputLogFileName + ".old");
if (!this.OutFileDisabled)
{
this.MoveFile(this.OutputLogFileName, this.OutputLogFileName + ".old");
}
if (!ErrFileDisabled)
MoveFile(ErrorLogFileName, ErrorLogFileName + ".old");
if (!this.ErrFileDisabled)
{
this.MoveFile(this.ErrorLogFileName, this.ErrorLogFileName + ".old");
}
base.log(outputReader, errorReader);
base.Log(outputReader, errorReader);
}
}
public class RollingSizeTimeLogAppender : AbstractFileLogAppender
{
public static int BYTES_PER_KB = 1024;
public int SizeTheshold { get; private set; }
public string FilePattern { get; private set; }
public TimeSpan? AutoRollAtTime { get; private set; }
public int? ZipOlderThanNumDays { get; private set; }
public string ZipDateFormat { get; private set; }
public const int BytesPerKB = 1024;
public int SizeThreshold { get; }
public string FilePattern { get; }
public TimeSpan? AutoRollAtTime { get; }
public int? ZipOlderThanNumDays { get; }
public string ZipDateFormat { get; }
public RollingSizeTimeLogAppender(
string logDirectory,
@ -341,21 +357,21 @@ namespace winsw
string zipdateformat)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{
SizeTheshold = sizeThreshold;
FilePattern = filePattern;
AutoRollAtTime = autoRollAtTime;
ZipOlderThanNumDays = zipolderthannumdays;
ZipDateFormat = zipdateformat;
this.SizeThreshold = sizeThreshold;
this.FilePattern = filePattern;
this.AutoRollAtTime = autoRollAtTime;
this.ZipOlderThanNumDays = zipolderthannumdays;
this.ZipDateFormat = zipdateformat;
}
protected override void LogOutput(StreamReader outputReader)
{
new Thread(() => CopyStreamWithRotation(outputReader, OutFilePattern)).Start();
new Thread(() => this.CopyStreamWithRotation(outputReader, this.OutFilePattern)).Start();
}
protected override void LogError(StreamReader errorReader)
{
new Thread(() => CopyStreamWithRotation(errorReader, ErrFilePattern)).Start();
new Thread(() => this.CopyStreamWithRotation(errorReader, this.ErrFilePattern)).Start();
}
private void CopyStreamWithRotation(StreamReader reader, string extension)
@ -363,18 +379,18 @@ namespace winsw
// lock required as the timer thread and the thread that will write to the stream could try and access the file stream at the same time
var fileLock = new object();
var baseDirectory = Path.GetDirectoryName(BaseLogFileName)!;
var baseFileName = Path.GetFileName(BaseLogFileName);
var logFile = BaseLogFileName + extension;
var baseDirectory = Path.GetDirectoryName(this.BaseLogFileName)!;
var baseFileName = Path.GetFileName(this.BaseLogFileName);
var logFile = this.BaseLogFileName + extension;
var writer = CreateWriter(new FileStream(logFile, FileMode.Append));
var writer = this.CreateWriter(new FileStream(logFile, FileMode.Append));
var fileLength = new FileInfo(logFile).Length;
// We auto roll at time is configured then we need to create a timer and wait until time is elasped and roll the file over
if (AutoRollAtTime is TimeSpan autoRollAtTime)
if (this.AutoRollAtTime is TimeSpan autoRollAtTime)
{
// Run at start
var tickTime = SetupRollTimer(autoRollAtTime);
var tickTime = this.SetupRollTimer(autoRollAtTime);
var timer = new System.Timers.Timer(tickTime);
timer.Elapsed += (s, e) =>
{
@ -386,25 +402,25 @@ namespace winsw
writer.Dispose();
var now = DateTime.Now.AddDays(-1);
var nextFileNumber = GetNextFileNumber(extension, baseDirectory, baseFileName, now);
var nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(FilePattern), nextFileNumber, extension));
var nextFileNumber = this.GetNextFileNumber(extension, baseDirectory, baseFileName, now);
var nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension));
File.Move(logFile, nextFileName);
writer = CreateWriter(new FileStream(logFile, FileMode.Create));
writer = this.CreateWriter(new FileStream(logFile, FileMode.Create));
fileLength = new FileInfo(logFile).Length;
}
// Next day so check if file can be zipped
ZipFiles(baseDirectory, extension, baseFileName);
this.ZipFiles(baseDirectory, extension, baseFileName);
}
catch (Exception ex)
{
EventLogger.LogEvent($"Failed to to trigger auto roll at time event due to: {ex.Message}");
this.EventLogger.LogEvent($"Failed to to trigger auto roll at time event due to: {ex.Message}");
}
finally
{
// Recalculate the next interval
timer.Interval = SetupRollTimer(autoRollAtTime);
timer.Interval = this.SetupRollTimer(autoRollAtTime);
timer.Start();
}
};
@ -417,26 +433,26 @@ namespace winsw
lock (fileLock)
{
int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char);
if (fileLength + lengthToWrite > SizeTheshold)
if (fileLength + lengthToWrite > this.SizeThreshold)
{
try
{
// roll file
var now = DateTime.Now;
var nextFileNumber = GetNextFileNumber(extension, baseDirectory, baseFileName, now);
var nextFileName =
Path.Combine(baseDirectory,
string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(FilePattern), nextFileNumber, extension));
var nextFileNumber = this.GetNextFileNumber(extension, baseDirectory, baseFileName, now);
var nextFileName = Path.Combine(
baseDirectory,
string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension));
File.Move(logFile, nextFileName);
// even if the log rotation fails, create a new one, or else
// we'll infinitely try to roll.
writer = CreateWriter(new FileStream(logFile, FileMode.Create));
writer = this.CreateWriter(new FileStream(logFile, FileMode.Create));
fileLength = new FileInfo(logFile).Length;
}
catch (Exception e)
{
EventLogger.LogEvent($"Failed to roll size time log: {e.Message}");
this.EventLogger.LogEvent($"Failed to roll size time log: {e.Message}");
}
}
@ -451,28 +467,32 @@ namespace winsw
private void ZipFiles(string directory, string fileExtension, string zipFileBaseName)
{
if (ZipOlderThanNumDays is null || ZipOlderThanNumDays <= 0)
if (this.ZipOlderThanNumDays is null || this.ZipOlderThanNumDays <= 0)
{
return;
}
try
{
foreach (string path in Directory.GetFiles(directory, "*" + fileExtension))
{
var fileInfo = new FileInfo(path);
if (fileInfo.LastWriteTimeUtc >= DateTime.UtcNow.AddDays(-ZipOlderThanNumDays.Value))
if (fileInfo.LastWriteTimeUtc >= DateTime.UtcNow.AddDays(-this.ZipOlderThanNumDays.Value))
{
continue;
}
string sourceFileName = Path.GetFileName(path);
string zipFilePattern = fileInfo.LastAccessTimeUtc.ToString(ZipDateFormat);
string zipFilePattern = fileInfo.LastAccessTimeUtc.ToString(this.ZipDateFormat);
string zipFilePath = Path.Combine(directory, $"{zipFileBaseName}.{zipFilePattern}.zip");
ZipOneFile(path, sourceFileName, zipFilePath);
this.ZipOneFile(path, sourceFileName, zipFilePath);
File.Delete(path);
}
}
catch (Exception e)
{
EventLogger.LogEvent($"Failed to Zip files. Error {e.Message}");
this.EventLogger.LogEvent($"Failed to Zip files. Error {e.Message}");
}
}
@ -490,7 +510,7 @@ namespace winsw
}
catch (Exception e)
{
EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}");
this.EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}");
}
finally
{
@ -510,7 +530,9 @@ namespace winsw
autoRollAtTime.Seconds,
0);
if (nowTime > scheduledTime)
{
scheduledTime = scheduledTime.AddDays(1);
}
double tickTime = (scheduledTime - DateTime.Now).TotalMilliseconds;
return tickTime;
@ -519,7 +541,7 @@ namespace winsw
private int GetNextFileNumber(string ext, string baseDirectory, string baseFileName, DateTime now)
{
var nextFileNumber = 0;
var files = Directory.GetFiles(baseDirectory, string.Format("{0}.{1}.#*{2}", baseFileName, now.ToString(FilePattern), ext));
var files = Directory.GetFiles(baseDirectory, string.Format("{0}.{1}.#*{2}", baseFileName, now.ToString(this.FilePattern), ext));
if (files.Length == 0)
{
nextFileNumber = 1;
@ -533,11 +555,12 @@ namespace winsw
var filenameOnly = Path.GetFileNameWithoutExtension(f);
var hashIndex = filenameOnly.IndexOf('#');
var lastNumberAsString = filenameOnly.Substring(hashIndex + 1, 4);
// var lastNumberAsString = filenameOnly.Substring(filenameOnly.Length - 4, 4);
if (int.TryParse(lastNumberAsString, out int lastNumber))
{
if (lastNumber > nextFileNumber)
{
nextFileNumber = lastNumber;
}
}
else
{
@ -551,7 +574,9 @@ namespace winsw
}
if (nextFileNumber == 0)
{
throw new IOException("Cannot roll the file because matching pattern not found");
}
nextFileNumber++;
}

View File

@ -1,6 +1,6 @@
using System.Diagnostics;
namespace winsw.Logging
namespace WinSW.Logging
{
/// <summary>
/// Indicates that the class may reference the event log
@ -11,6 +11,6 @@ namespace winsw.Logging
/// Locates Event Log for the service.
/// </summary>
/// <returns>Event Log or null if it is not avilable</returns>
EventLog? locate();
EventLog? Locate();
}
}

View File

@ -2,7 +2,7 @@
using log4net.Appender;
using log4net.Core;
namespace winsw.Logging
namespace WinSW.Logging
{
/// <summary>
/// Implementes service Event log appender for log4j.
@ -11,18 +11,18 @@ namespace winsw.Logging
public class ServiceEventLogAppender : AppenderSkeleton
{
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public IServiceEventLogProvider provider { get; set; }
public IServiceEventLogProvider Provider { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
override protected void Append(LoggingEvent loggingEvent)
protected override void Append(LoggingEvent loggingEvent)
{
EventLog? eventLog = provider.locate();
EventLog? eventLog = this.Provider.Locate();
// We write the event iff the provider is ready
eventLog?.WriteEntry(loggingEvent.RenderedMessage, toEventLogEntryType(loggingEvent.Level));
eventLog?.WriteEntry(loggingEvent.RenderedMessage, ToEventLogEntryType(loggingEvent.Level));
}
private static EventLogEntryType toEventLogEntryType(Level level)
private static EventLogEntryType ToEventLogEntryType(Level level)
{
if (level.Value >= Level.Error.Value)
{

View File

@ -1,6 +1,8 @@
using System.Runtime.InteropServices;
#pragma warning disable SA1310 // Field names should not contain underscore
namespace winsw.Native
using System.Runtime.InteropServices;
namespace WinSW.Native
{
internal static class ConsoleApis
{

View File

@ -1,7 +1,9 @@
using System;
#pragma warning disable SA1310 // Field names should not contain underscore
using System;
using System.Runtime.InteropServices;
namespace winsw.Native
namespace WinSW.Native
{
internal static class CredentialApis
{

View File

@ -1,4 +1,6 @@
namespace winsw.Native
#pragma warning disable SA1310 // Field names should not contain underscore
namespace WinSW.Native
{
internal static class Errors
{

View File

@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
namespace winsw.Native
namespace WinSW.Native
{
internal static class HandleApis
{

View File

@ -1,4 +1,4 @@
namespace winsw.Native
namespace WinSW.Native
{
internal static class Libraries
{

View File

@ -1,8 +1,10 @@
using System;
#pragma warning disable SA1310 // Field names should not contain underscore
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace winsw.Native
namespace WinSW.Native
{
internal static class ProcessApis
{
@ -46,12 +48,14 @@ namespace winsw.Native
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PROCESS_BASIC_INFORMATION
{
#pragma warning disable SA1306 // Field names should begin with lower-case letter
private readonly IntPtr Reserved1;
private readonly IntPtr PebBaseAddress;
private readonly IntPtr Reserved2_1;
private readonly IntPtr Reserved2_2;
internal readonly IntPtr UniqueProcessId;
internal readonly IntPtr InheritedFromUniqueProcessId;
#pragma warning restore SA1306 // Field names should begin with lower-case letter
}
internal struct PROCESS_INFORMATION

View File

@ -1,9 +1,9 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using static winsw.Native.SecurityApis;
using static WinSW.Native.SecurityApis;
namespace winsw.Native
namespace WinSW.Native
{
internal static class Security
{

View File

@ -1,7 +1,9 @@
using System;
#pragma warning disable SA1310 // Field names should not contain underscore
using System;
using System.Runtime.InteropServices;
namespace winsw.Native
namespace WinSW.Native
{
internal static class SecurityApis
{

View File

@ -3,9 +3,9 @@ using System.ComponentModel;
using System.Security.AccessControl;
using System.ServiceProcess;
using System.Text;
using static winsw.Native.ServiceApis;
using static WinSW.Native.ServiceApis;
namespace winsw.Native
namespace WinSW.Native
{
public enum SC_ACTION_TYPE
{
@ -198,7 +198,8 @@ namespace winsw.Native
{
fixed (SC_ACTION* actionsPtr = actions)
{
if (!ChangeServiceConfig2(this.handle,
if (!ChangeServiceConfig2(
this.handle,
ServiceConfigInfoLevels.FAILURE_ACTIONS,
new SERVICE_FAILURE_ACTIONS
{

View File

@ -4,7 +4,7 @@ using System.Security.AccessControl;
using System.ServiceProcess;
using System.Text;
namespace winsw.Native
namespace WinSW.Native
{
internal static class ServiceApis
{

View File

@ -2,7 +2,7 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace winsw.Native
namespace WinSW.Native
{
internal static class Throw
{

View File

@ -3,10 +3,14 @@ namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
internal sealed class AllowNullAttribute : Attribute { }
internal sealed class AllowNullAttribute : Attribute
{
}
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
internal sealed class MaybeNullAttribute : Attribute { }
internal sealed class MaybeNullAttribute : Attribute
{
}
}
#endif

View File

@ -1,30 +1,26 @@
using System;
// ReSharper disable InconsistentNaming
namespace winsw
namespace WinSW
{
/**
* This is largely borrowed from the logback Rolling Calendar.
**/
// This is largely borrowed from the logback Rolling Calendar.
public class PeriodicRollingCalendar
{
private readonly string _format;
private readonly long _period;
private DateTime _currentRoll;
private DateTime _nextRoll;
private readonly string format;
private readonly long period;
private DateTime currentRoll;
private DateTime nextRoll;
public PeriodicRollingCalendar(string format, long period)
{
_format = format;
_period = period;
_currentRoll = DateTime.Now;
this.format = format;
this.period = period;
this.currentRoll = DateTime.Now;
}
public void init()
public void Init()
{
periodicityType = determinePeriodicityType();
_nextRoll = nextTriggeringTime(_currentRoll, _period);
this.Periodicity = this.DeterminePeriodicityType();
this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period);
}
public enum PeriodicityType
@ -37,23 +33,23 @@ namespace winsw
TOP_OF_DAY
}
private static readonly PeriodicityType[] VALID_ORDERED_LIST =
private static readonly PeriodicityType[] ValidOrderedList =
{
PeriodicityType.TOP_OF_MILLISECOND, PeriodicityType.TOP_OF_SECOND, PeriodicityType.TOP_OF_MINUTE, PeriodicityType.TOP_OF_HOUR, PeriodicityType.TOP_OF_DAY
};
private PeriodicityType determinePeriodicityType()
private PeriodicityType DeterminePeriodicityType()
{
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(_format, _period);
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.format, this.period);
DateTime epoch = new DateTime(1970, 1, 1);
foreach (PeriodicityType i in VALID_ORDERED_LIST)
foreach (PeriodicityType i in ValidOrderedList)
{
string r0 = epoch.ToString(_format);
periodicRollingCalendar.periodicityType = i;
string r0 = epoch.ToString(this.format);
periodicRollingCalendar.Periodicity = i;
DateTime next = periodicRollingCalendar.nextTriggeringTime(epoch, 1);
string r1 = next.ToString(_format);
DateTime next = periodicRollingCalendar.NextTriggeringTime(epoch, 1);
string r1 = next.ToString(this.format);
if (r0 != r1)
{
@ -64,7 +60,7 @@ namespace winsw
return PeriodicityType.ERRONEOUS;
}
private DateTime nextTriggeringTime(DateTime input, long increment) => periodicityType switch
private DateTime NextTriggeringTime(DateTime input, long increment) => this.Periodicity switch
{
PeriodicityType.TOP_OF_MILLISECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond)
@ -86,20 +82,20 @@ namespace winsw
new DateTime(input.Year, input.Month, input.Day)
.AddDays(increment),
_ => throw new Exception("invalid periodicity type: " + periodicityType),
_ => throw new Exception("invalid periodicity type: " + this.Periodicity),
};
public PeriodicityType periodicityType { get; set; }
public PeriodicityType Periodicity { get; set; }
public bool shouldRoll
public bool ShouldRoll
{
get
{
DateTime now = DateTime.Now;
if (now > _nextRoll)
if (now > this.nextRoll)
{
_currentRoll = now;
_nextRoll = nextTriggeringTime(now, _period);
this.currentRoll = now;
this.nextRoll = this.NextTriggeringTime(now, this.period);
return true;
}
@ -107,6 +103,6 @@ namespace winsw
}
}
public string format => _currentRoll.ToString(_format);
public string Format => this.currentRoll.ToString(this.format);
}
}

View File

@ -5,18 +5,17 @@ using System.IO;
using System.ServiceProcess;
using System.Text;
using System.Xml;
using winsw.Configuration;
using winsw.Native;
using winsw.Util;
using WinSW.Configuration;
using WinSW.Native;
using WinSW.Util;
namespace winsw
namespace WinSW
{
/// <summary>
/// In-memory representation of the configuration file.
/// </summary>
public class ServiceDescriptor : IWinSWConfiguration
{
// ReSharper disable once InconsistentNaming
protected readonly XmlDocument dom = new XmlDocument();
private readonly Dictionary<string, string> environmentVariables;
@ -45,29 +44,35 @@ namespace winsw
// find co-located configuration xml. We search up to the ancestor directories to simplify debugging,
// as well as trimming off ".vshost" suffix (which is used during debugging)
// Get the first parent to go into the recursive loop
string p = ExecutablePath;
string p = this.ExecutablePath;
string baseName = Path.GetFileNameWithoutExtension(p);
if (baseName.EndsWith(".vshost"))
{
baseName = baseName.Substring(0, baseName.Length - 7);
}
DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(p));
while (true)
{
if (File.Exists(Path.Combine(d.FullName, baseName + ".xml")))
{
break;
}
if (d.Parent is null)
{
throw new FileNotFoundException("Unable to locate " + baseName + ".xml file within executable directory or any parents");
}
d = d.Parent;
}
BaseName = baseName;
BasePath = Path.Combine(d.FullName, BaseName);
this.BaseName = baseName;
this.BasePath = Path.Combine(d.FullName, this.BaseName);
try
{
dom.Load(BasePath + ".xml");
this.dom.Load(this.BasePath + ".xml");
}
catch (XmlException e)
{
@ -78,13 +83,13 @@ namespace winsw
Environment.SetEnvironmentVariable("BASE", d.FullName);
// ditto for ID
Environment.SetEnvironmentVariable("SERVICE_ID", Id);
Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
// New name
Environment.SetEnvironmentVariable(WinSWSystem.ENVVAR_NAME_EXECUTABLE_PATH, ExecutablePath);
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
// Also inject system environment variables
Environment.SetEnvironmentVariable(WinSWSystem.ENVVAR_NAME_SERVICE_ID, Id);
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
this.environmentVariables = this.LoadEnvironmentVariables();
}
@ -101,8 +106,7 @@ namespace winsw
this.environmentVariables = this.LoadEnvironmentVariables();
}
// ReSharper disable once InconsistentNaming
public static ServiceDescriptor FromXML(string xml)
public static ServiceDescriptor FromXml(string xml)
{
var dom = new XmlDocument();
dom.LoadXml(xml);
@ -111,21 +115,23 @@ namespace winsw
private string SingleElement(string tagName)
{
return SingleElement(tagName, false)!;
return this.SingleElement(tagName, false)!;
}
private string? SingleElement(string tagName, bool optional)
{
XmlNode? n = dom.SelectSingleNode("//" + tagName);
XmlNode? n = this.dom.SelectSingleNode("//" + tagName);
if (n is null && !optional)
{
throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
}
return n is null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
}
private bool SingleBoolElement(string tagName, bool defaultValue)
{
XmlNode? e = dom.SelectSingleNode("//" + tagName);
XmlNode? e = this.dom.SelectSingleNode("//" + tagName);
return e is null ? defaultValue : bool.Parse(e.InnerText);
}
@ -139,8 +145,8 @@ namespace winsw
private TimeSpan SingleTimeSpanElement(XmlNode parent, string tagName, TimeSpan defaultValue)
{
string? value = SingleElement(tagName, true);
return value is null ? defaultValue : ParseTimeSpan(value);
string? value = this.SingleElement(tagName, true);
return value is null ? defaultValue : this.ParseTimeSpan(value);
}
private TimeSpan ParseTimeSpan(string v)
@ -175,14 +181,14 @@ namespace winsw
/// <summary>
/// Path to the executable.
/// </summary>
public string Executable => SingleElement("executable");
public string Executable => this.SingleElement("executable");
public bool HideWindow => SingleBoolElement("hidewindow", Defaults.HideWindow);
public bool HideWindow => this.SingleBoolElement("hidewindow", Defaults.HideWindow);
/// <summary>
/// Optionally specify a different Path to an executable to shutdown the service.
/// </summary>
public string? StopExecutable => SingleElement("stopexecutable", true);
public string? StopExecutable => this.SingleElement("stopexecutable", true);
/// <summary>
/// <c>arguments</c> or multiple optional <c>argument</c> elements which overrule the arguments element.
@ -191,14 +197,14 @@ namespace winsw
{
get
{
string? arguments = AppendTags("argument", null);
string? arguments = this.AppendTags("argument", null);
if (!(arguments is null))
{
return arguments;
}
XmlNode? argumentsNode = dom.SelectSingleNode("//arguments");
XmlNode? argumentsNode = this.dom.SelectSingleNode("//arguments");
return argumentsNode is null ? Defaults.Arguments : Environment.ExpandEnvironmentVariables(argumentsNode.InnerText);
}
@ -211,14 +217,14 @@ namespace winsw
{
get
{
string? startArguments = AppendTags("startargument", null);
string? startArguments = this.AppendTags("startargument", null);
if (!(startArguments is null))
{
return startArguments;
}
XmlNode? startArgumentsNode = dom.SelectSingleNode("//startarguments");
XmlNode? startArgumentsNode = this.dom.SelectSingleNode("//startarguments");
return startArgumentsNode is null ? null : Environment.ExpandEnvironmentVariables(startArgumentsNode.InnerText);
}
@ -231,14 +237,14 @@ namespace winsw
{
get
{
string? stopArguments = AppendTags("stopargument", null);
string? stopArguments = this.AppendTags("stopargument", null);
if (!(stopArguments is null))
{
return stopArguments;
}
XmlNode? stopArgumentsNode = dom.SelectSingleNode("//stoparguments");
XmlNode? stopArgumentsNode = this.dom.SelectSingleNode("//stoparguments");
return stopArgumentsNode is null ? null : Environment.ExpandEnvironmentVariables(stopArgumentsNode.InnerText);
}
@ -248,7 +254,7 @@ namespace winsw
{
get
{
var wd = SingleElement("workingdirectory", true);
var wd = this.SingleElement("workingdirectory", true);
return string.IsNullOrEmpty(wd) ? Defaults.WorkingDirectory : wd!;
}
}
@ -257,7 +263,7 @@ namespace winsw
{
get
{
XmlNode? argumentNode = ExtensionsConfiguration;
XmlNode? argumentNode = this.ExtensionsConfiguration;
XmlNodeList? extensions = argumentNode?.SelectNodes("extension");
if (extensions is null)
{
@ -274,7 +280,7 @@ namespace winsw
}
}
public XmlNode? ExtensionsConfiguration => dom.SelectSingleNode("//extensions");
public XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions");
/// <summary>
/// Combines the contents of all the elements of the given name,
@ -282,7 +288,7 @@ namespace winsw
/// </summary>
private string? AppendTags(string tagName, string? defaultValue = null)
{
XmlNode? argumentNode = dom.SelectSingleNode("//" + tagName);
XmlNode? argumentNode = this.dom.SelectSingleNode("//" + tagName);
if (argumentNode is null)
{
return defaultValue;
@ -290,7 +296,7 @@ namespace winsw
StringBuilder arguments = new StringBuilder();
XmlNodeList argumentNodeList = dom.SelectNodes("//" + tagName);
XmlNodeList argumentNodeList = this.dom.SelectNodes("//" + tagName);
for (int i = 0; i < argumentNodeList.Count; i++)
{
arguments.Append(' ');
@ -325,7 +331,7 @@ namespace winsw
{
get
{
XmlNode? loggingNode = dom.SelectSingleNode("//logpath");
XmlNode? loggingNode = this.dom.SelectSingleNode("//logpath");
return loggingNode is null
? Defaults.LogDirectory
@ -340,7 +346,7 @@ namespace winsw
string? mode = null;
// first, backward compatibility with older configuration
XmlElement? e = (XmlElement?)dom.SelectSingleNode("//logmode");
XmlElement? e = (XmlElement?)this.dom.SelectSingleNode("//logmode");
if (e != null)
{
mode = e.InnerText;
@ -348,9 +354,11 @@ namespace winsw
else
{
// this is more modern way, to support nested elements as configuration
e = (XmlElement?)dom.SelectSingleNode("//log");
e = (XmlElement?)this.dom.SelectSingleNode("//log");
if (e != null)
{
mode = e.GetAttribute("mode");
}
}
return mode ?? Defaults.LogMode;
@ -361,21 +369,21 @@ namespace winsw
{
get
{
XmlNode? loggingName = dom.SelectSingleNode("//logname");
XmlNode? loggingName = this.dom.SelectSingleNode("//logname");
return loggingName is null ? BaseName : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
return loggingName is null ? this.BaseName : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
}
}
public bool OutFileDisabled => SingleBoolElement("outfiledisabled", Defaults.OutFileDisabled);
public bool OutFileDisabled => this.SingleBoolElement("outfiledisabled", Defaults.OutFileDisabled);
public bool ErrFileDisabled => SingleBoolElement("errfiledisabled", Defaults.ErrFileDisabled);
public bool ErrFileDisabled => this.SingleBoolElement("errfiledisabled", Defaults.ErrFileDisabled);
public string OutFilePattern
{
get
{
XmlNode? loggingName = dom.SelectSingleNode("//outfilepattern");
XmlNode? loggingName = this.dom.SelectSingleNode("//outfilepattern");
return loggingName is null ? Defaults.OutFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
}
@ -385,7 +393,7 @@ namespace winsw
{
get
{
XmlNode? loggingName = dom.SelectSingleNode("//errfilepattern");
XmlNode? loggingName = this.dom.SelectSingleNode("//errfilepattern");
return loggingName is null ? Defaults.ErrFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
}
@ -395,25 +403,25 @@ namespace winsw
{
get
{
XmlElement? e = (XmlElement?)dom.SelectSingleNode("//logmode");
XmlElement? e = (XmlElement?)this.dom.SelectSingleNode("//logmode");
// this is more modern way, to support nested elements as configuration
e ??= (XmlElement?)dom.SelectSingleNode("//log")!; // WARNING: NRE
e ??= (XmlElement?)this.dom.SelectSingleNode("//log")!; // WARNING: NRE
int sizeThreshold;
switch (LogMode)
switch (this.LogMode)
{
case "rotate":
return new SizeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
return new SizeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "none":
return new IgnoreLogAppender();
case "reset":
return new ResetLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
return new ResetLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll":
return new RollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
return new RollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll-by-time":
XmlNode? patternNode = e.SelectSingleNode("pattern");
@ -423,19 +431,19 @@ namespace winsw
}
var pattern = patternNode.InnerText;
int period = SingleIntElement(e, "period", 1);
return new TimeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, pattern, period);
int period = this.SingleIntElement(e, "period", 1);
return new TimeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, pattern, period);
case "roll-by-size":
sizeThreshold = SingleIntElement(e, "sizeThreshold", 10 * 1024) * SizeBasedRollingLogAppender.BYTES_PER_KB;
int keepFiles = SingleIntElement(e, "keepFiles", SizeBasedRollingLogAppender.DEFAULT_FILES_TO_KEEP);
return new SizeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, sizeThreshold, keepFiles);
sizeThreshold = this.SingleIntElement(e, "sizeThreshold", 10 * 1024) * SizeBasedRollingLogAppender.BytesPerKB;
int keepFiles = this.SingleIntElement(e, "keepFiles", SizeBasedRollingLogAppender.DefaultFilesToKeep);
return new SizeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, sizeThreshold, keepFiles);
case "append":
return new DefaultLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
return new DefaultLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll-by-size-time":
sizeThreshold = SingleIntElement(e, "sizeThreshold", 10 * 1024) * RollingSizeTimeLogAppender.BYTES_PER_KB;
sizeThreshold = this.SingleIntElement(e, "sizeThreshold", 10 * 1024) * RollingSizeTimeLogAppender.BytesPerKB;
XmlNode? filePatternNode = e.SelectSingleNode("pattern");
if (filePatternNode is null)
{
@ -448,7 +456,9 @@ namespace winsw
{
// validate it
if (!TimeSpan.TryParse(autoRollAtTimeNode.InnerText, out TimeSpan autoRollAtTimeValue))
{
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but autoRollAtTime does not match the TimeSpan format HH:mm:ss found in configuration XML.");
}
autoRollAtTime = autoRollAtTimeValue;
}
@ -459,7 +469,9 @@ namespace winsw
{
// validate it
if (!int.TryParse(zipolderthannumdaysNode.InnerText, out int zipolderthannumdaysValue))
{
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but zipOlderThanNumDays does not match the int format found in configuration XML.");
}
zipolderthannumdays = zipolderthannumdaysValue;
}
@ -467,10 +479,10 @@ namespace winsw
XmlNode? zipdateformatNode = e.SelectSingleNode("zipDateFormat");
string zipdateformat = zipdateformatNode is null ? "yyyyMM" : zipdateformatNode.InnerText;
return new RollingSizeTimeLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, sizeThreshold, filePatternNode.InnerText, autoRollAtTime, zipolderthannumdays, zipdateformat);
return new RollingSizeTimeLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, sizeThreshold, filePatternNode.InnerText, autoRollAtTime, zipolderthannumdays, zipdateformat);
default:
throw new InvalidDataException("Undefined logging mode: " + LogMode);
throw new InvalidDataException("Undefined logging mode: " + this.LogMode);
}
}
}
@ -482,7 +494,7 @@ namespace winsw
{
get
{
XmlNodeList? nodeList = dom.SelectNodes("//depend");
XmlNodeList? nodeList = this.dom.SelectNodes("//depend");
if (nodeList is null)
{
return Defaults.ServiceDependencies;
@ -498,11 +510,11 @@ namespace winsw
}
}
public string Id => SingleElement("id");
public string Id => this.SingleElement("id");
public string Caption => SingleElement("name");
public string Caption => this.SingleElement("name");
public string Description => SingleElement("description");
public string Description => this.SingleElement("description");
/// <summary>
/// Start mode of the Service
@ -511,9 +523,11 @@ namespace winsw
{
get
{
string? p = SingleElement("startmode", true);
string? p = this.SingleElement("startmode", true);
if (p is null)
{
return Defaults.StartMode;
}
try
{
@ -536,32 +550,32 @@ namespace winsw
/// True if the service should be installed with the DelayedAutoStart flag.
/// This setting will be applyed only during the install command and only when the Automatic start mode is configured.
/// </summary>
public bool DelayedAutoStart => dom.SelectSingleNode("//delayedAutoStart") != null;
public bool DelayedAutoStart => this.dom.SelectSingleNode("//delayedAutoStart") != null;
/// <summary>
/// True if the service should beep when finished on shutdown.
/// This doesn't work on some OSes. See http://msdn.microsoft.com/en-us/library/ms679277%28VS.85%29.aspx
/// </summary>
public bool BeepOnShutdown => dom.SelectSingleNode("//beeponshutdown") != null;
public bool BeepOnShutdown => this.dom.SelectSingleNode("//beeponshutdown") != null;
/// <summary>
/// The estimated time required for a pending stop operation (default 15 secs).
/// Before the specified amount of time has elapsed, the service should make its next call to the SetServiceStatus function
/// with either an incremented checkPoint value or a change in currentState. (see http://msdn.microsoft.com/en-us/library/ms685996.aspx)
/// </summary>
public TimeSpan WaitHint => SingleTimeSpanElement(dom, "waithint", Defaults.WaitHint);
public TimeSpan WaitHint => this.SingleTimeSpanElement(this.dom, "waithint", Defaults.WaitHint);
/// <summary>
/// The time before the service should make its next call to the SetServiceStatus function
/// with an incremented checkPoint value (default 1 sec).
/// Do not wait longer than the wait hint. A good interval is one-tenth of the wait hint but not less than 1 second and not more than 10 seconds.
/// </summary>
public TimeSpan SleepTime => SingleTimeSpanElement(dom, "sleeptime", Defaults.SleepTime);
public TimeSpan SleepTime => this.SingleTimeSpanElement(this.dom, "sleeptime", Defaults.SleepTime);
/// <summary>
/// True if the service can interact with the desktop.
/// </summary>
public bool Interactive => dom.SelectSingleNode("//interactive") != null;
public bool Interactive => this.dom.SelectSingleNode("//interactive") != null;
/// <summary>
/// Environment variable overrides
@ -576,7 +590,7 @@ namespace winsw
{
get
{
XmlNodeList? nodeList = dom.SelectNodes("//download");
XmlNodeList? nodeList = this.dom.SelectNodes("//download");
if (nodeList is null)
{
return Defaults.Downloads;
@ -599,7 +613,7 @@ namespace winsw
{
get
{
XmlNodeList? childNodes = dom.SelectNodes("//onfailure");
XmlNodeList? childNodes = this.dom.SelectNodes("//onfailure");
if (childNodes is null)
{
return new SC_ACTION[0];
@ -618,18 +632,18 @@ namespace winsw
_ => throw new Exception("Invalid failure action: " + action)
};
XmlAttribute? delay = node.Attributes["delay"];
result[i] = new SC_ACTION(type, delay != null ? ParseTimeSpan(delay.Value) : TimeSpan.Zero);
result[i] = new SC_ACTION(type, delay != null ? this.ParseTimeSpan(delay.Value) : TimeSpan.Zero);
}
return result;
}
}
public TimeSpan ResetFailureAfter => SingleTimeSpanElement(dom, "resetfailure", Defaults.ResetFailureAfter);
public TimeSpan ResetFailureAfter => this.SingleTimeSpanElement(this.dom, "resetfailure", Defaults.ResetFailureAfter);
protected string? GetServiceAccountPart(string subNodeName)
{
XmlNode? node = dom.SelectSingleNode("//serviceaccount");
XmlNode? node = this.dom.SelectSingleNode("//serviceaccount");
if (node != null)
{
@ -643,13 +657,13 @@ namespace winsw
return null;
}
public string? ServiceAccountPrompt => GetServiceAccountPart("prompt")?.ToLowerInvariant();
public string? ServiceAccountPrompt => this.GetServiceAccountPart("prompt")?.ToLowerInvariant();
protected string? AllowServiceLogon => GetServiceAccountPart("allowservicelogon");
protected string? AllowServiceLogon => this.GetServiceAccountPart("allowservicelogon");
public string? ServiceAccountPassword => GetServiceAccountPart("password");
public string? ServiceAccountPassword => this.GetServiceAccountPart("password");
public string? ServiceAccountUserName => GetServiceAccountPart("username");
public string? ServiceAccountUserName => this.GetServiceAccountPart("username");
public bool HasServiceAccount()
{
@ -660,9 +674,9 @@ namespace winsw
{
get
{
if (AllowServiceLogon != null)
if (this.AllowServiceLogon != null)
{
if (bool.TryParse(AllowServiceLogon, out bool parsedvalue))
if (bool.TryParse(this.AllowServiceLogon, out bool parsedvalue))
{
return parsedvalue;
}
@ -675,7 +689,7 @@ namespace winsw
/// <summary>
/// Time to wait for the service to gracefully shutdown the executable before we forcibly kill it
/// </summary>
public TimeSpan StopTimeout => SingleTimeSpanElement(dom, "stoptimeout", Defaults.StopTimeout);
public TimeSpan StopTimeout => this.SingleTimeSpanElement(this.dom, "stoptimeout", Defaults.StopTimeout);
/// <summary>
/// Desired process priority or null if not specified.
@ -684,19 +698,21 @@ namespace winsw
{
get
{
string? p = SingleElement("priority", true);
string? p = this.SingleElement("priority", true);
if (p is null)
{
return Defaults.Priority;
}
return (ProcessPriorityClass)Enum.Parse(typeof(ProcessPriorityClass), p, true);
}
}
public string? SecurityDescriptor => SingleElement("securityDescriptor", true);
public string? SecurityDescriptor => this.SingleElement("securityDescriptor", true);
private Dictionary<string, string> LoadEnvironmentVariables()
{
XmlNodeList nodeList = dom.SelectNodes("//env");
XmlNodeList nodeList = this.dom.SelectNodes("//env");
Dictionary<string, string> environment = new Dictionary<string, string>(nodeList.Count);
for (int i = 0; i < nodeList.Count; i++)
{

View File

@ -6,7 +6,7 @@ using System.IO;
using System.Runtime.InteropServices;
#endif
namespace winsw.Util
namespace WinSW.Util
{
public static class FileHelper
{

View File

@ -4,9 +4,9 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using log4net;
using static winsw.Native.ProcessApis;
using static WinSW.Native.ProcessApis;
namespace winsw.Util
namespace WinSW.Util
{
/// <summary>
/// Provides helper classes for Process Management
@ -73,8 +73,6 @@ namespace winsw.Util
/// Stops the process.
/// If the process cannot be stopped within the stop timeout, it gets killed
/// </summary>
/// <param name="pid">PID of the process</param>
/// <param name="stopTimeout">Stop timeout</param>
public static void StopProcess(Process process, TimeSpan stopTimeout)
{
Logger.Info("Stopping process " + process.Id);
@ -97,6 +95,7 @@ namespace winsw.Util
{
Logger.Warn("Process " + process.Id + " did not respond to Ctrl+C signal - Killing as fallback");
}
process.Kill();
}
catch (Exception ex)
@ -118,8 +117,6 @@ namespace winsw.Util
/// Terminate process and its children.
/// By default the child processes get terminated first.
/// </summary>
/// <param name="pid">Process PID</param>
/// <param name="stopTimeout">Stop timeout (for each process)</param>
public static void StopProcessTree(Process process, TimeSpan stopTimeout)
{
StopProcess(process, stopTimeout);
@ -135,14 +132,14 @@ namespace winsw.Util
/// Once the process exits, the callback will be invoked.
/// </summary>
/// <param name="processToStart">Process object to be used</param>
/// <param name="arguments">Arguments to be passed</param>
/// <param name="executable">Executable, which should be invoked</param>
/// <param name="arguments">Arguments to be passed</param>
/// <param name="envVars">Additional environment variables</param>
/// <param name="workingDirectory">Working directory</param>
/// <param name="priority">Priority</param>
/// <param name="callback">Completion callback. If null, the completion won't be monitored</param>
/// <param name="logHandler">Log handler. If enabled, logs will be redirected to the process and then reported</param>
/// <param name="redirectStdin">Redirect standard input</param>
/// <param name="logHandler">Log handler. If enabled, logs will be redirected to the process and then reported</param>
public static void StartProcessAndCallbackForExit(
Process processToStart,
string? executable = null,
@ -191,7 +188,7 @@ namespace winsw.Util
if (logHandler != null)
{
Logger.Debug("Forwarding logs of the process " + processToStart + " to " + logHandler);
logHandler.log(processToStart.StandardOutput, processToStart.StandardError);
logHandler.Log(processToStart.StandardOutput, processToStart.StandardError);
}
// monitor the completion of the process

View File

@ -4,9 +4,9 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using log4net;
using winsw.Native;
using WinSW.Native;
namespace winsw.Util
namespace WinSW.Util
{
internal static class SignalHelper
{

View File

@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Xml;
namespace winsw.Util
namespace WinSW.Util
{
public class XmlHelper
{
@ -19,7 +19,9 @@ namespace winsw.Util
{
XmlNode? n = node.SelectSingleNode(tagName);
if (n is null && !optional)
{
throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
}
return n is null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
}
@ -36,7 +38,9 @@ namespace winsw.Util
{
XmlNode? n = node.SelectSingleNode(tagName);
if (n is null && !optional)
{
throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
}
return n;
}
@ -69,7 +73,9 @@ namespace winsw.Util
public static TAttributeType SingleAttribute<TAttributeType>(XmlElement node, string attributeName, [AllowNull] TAttributeType defaultValue)
{
if (!node.HasAttribute(attributeName))
{
return defaultValue;
}
string rawValue = node.GetAttribute(attributeName);
string substitutedValue = Environment.ExpandEnvironmentVariables(rawValue);
@ -90,7 +96,9 @@ namespace winsw.Util
where TAttributeType : struct
{
if (!node.HasAttribute(attributeName))
{
return defaultValue;
}
string rawValue = node.GetAttribute(attributeName);
string substitutedValue = Environment.ExpandEnvironmentVariables(rawValue);
@ -100,7 +108,8 @@ namespace winsw.Util
}
catch (ArgumentException ex)
{
throw new InvalidDataException("Cannot parse <" + attributeName + "> Enum value from string '" + substitutedValue +
throw new InvalidDataException(
"Cannot parse <" + attributeName + "> Enum value from string '" + substitutedValue +
"'. Enum type: " + typeof(TAttributeType), ex);
}
}

View File

@ -11,6 +11,10 @@
<ItemGroup>
<PackageReference Include="log4net" Version="2.0.8" />
<PackageReference Include="stylecop.analyzers" Version="1.2.0-beta.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">

View File

@ -1,15 +1,17 @@
using System;
namespace winsw
namespace WinSW
{
public class WinSWException : Exception
{
public WinSWException(string message)
: base(message)
{ }
{
}
public WinSWException(string message, Exception innerException)
: base(message, innerException)
{ }
{
}
}
}

View File

@ -1,4 +1,4 @@
namespace winsw
namespace WinSW
{
/// <summary>
/// Class, which contains generic information about WinSW runtime.
@ -9,17 +9,17 @@
/// <summary>
/// Prefix for all environment variables being injected for WinSW
/// </summary>
public static readonly string SYSTEM_EVNVVAR_PREFIX = "WINSW_";
public static readonly string SystemEnvVarPrefix = "WINSW_";
/// <summary>
/// Variable, which points to the service ID.
/// It may be used to determine runaway processes.
/// </summary>
public static string ENVVAR_NAME_SERVICE_ID => SYSTEM_EVNVVAR_PREFIX + "SERVICE_ID";
public static string EnvVarNameServiceId => SystemEnvVarPrefix + "SERVICE_ID";
/// <summary>
/// Variable, which specifies path to the executable being launched by WinSW.
/// </summary>
public static string ENVVAR_NAME_EXECUTABLE_PATH => SYSTEM_EVNVVAR_PREFIX + "EXECUTABLE";
public static string EnvVarNameExecutablePath => SystemEnvVarPrefix + "EXECUTABLE";
}
}

View File

@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
namespace winsw.Plugins.RunawayProcessKiller
namespace WinSW.Plugins.RunawayProcessKiller
{
public partial class RunawayProcessKillerExtension
{

View File

@ -1,15 +1,14 @@
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using log4net;
using winsw.Extensions;
using winsw.Util;
using static winsw.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods;
using WinSW.Extensions;
using WinSW.Util;
using static WinSW.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods;
namespace winsw.Plugins.RunawayProcessKiller
namespace WinSW.Plugins.RunawayProcessKiller
{
public partial class RunawayProcessKillerExtension : AbstractWinSWExtension
{
@ -177,12 +176,13 @@ namespace winsw.Plugins.RunawayProcessKiller
{
// We expect the upper logic to process any errors
// TODO: a better parser API for types would be useful
Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!;
StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!));
ServiceId = descriptor.Id;
this.Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!;
this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!));
this.ServiceId = descriptor.Id;
// TODO: Consider making it documented
var checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true);
CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable);
this.CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable);
}
/// <summary>
@ -193,16 +193,16 @@ namespace winsw.Plugins.RunawayProcessKiller
{
// Read PID file from the disk
int pid;
if (File.Exists(Pidfile))
if (File.Exists(this.Pidfile))
{
string pidstring;
try
{
pidstring = File.ReadAllText(Pidfile);
pidstring = File.ReadAllText(this.Pidfile);
}
catch (Exception ex)
{
Logger.Error("Cannot read PID file from " + Pidfile, ex);
Logger.Error("Cannot read PID file from " + this.Pidfile, ex);
return;
}
@ -212,13 +212,13 @@ namespace winsw.Plugins.RunawayProcessKiller
}
catch (FormatException e)
{
Logger.Error("Invalid PID file number in '" + Pidfile + "'. The runaway process won't be checked", e);
Logger.Error("Invalid PID file number in '" + this.Pidfile + "'. The runaway process won't be checked", e);
return;
}
}
else
{
Logger.Warn("The requested PID file '" + Pidfile + "' does not exist. The runaway process won't be checked");
Logger.Warn("The requested PID file '" + this.Pidfile + "' does not exist. The runaway process won't be checked");
return;
}
@ -236,9 +236,9 @@ namespace winsw.Plugins.RunawayProcessKiller
}
// Ensure the process references the service
string expectedEnvVarName = WinSWSystem.ENVVAR_NAME_SERVICE_ID;
string expectedEnvVarName = WinSWSystem.EnvVarNameServiceId;
string? affiliatedServiceId = ReadEnvironmentVariable(proc.Handle, expectedEnvVarName);
if (affiliatedServiceId is null && CheckWinSWEnvironmentVariable)
if (affiliatedServiceId is null && this.CheckWinSWEnvironmentVariable)
{
Logger.Warn("The process " + pid + " has no " + expectedEnvVarName + " environment variable defined. "
+ "The process has not been started by WinSW, hence it won't be terminated.");
@ -247,10 +247,10 @@ namespace winsw.Plugins.RunawayProcessKiller
}
// Check the service ID value
if (CheckWinSWEnvironmentVariable && !ServiceId.Equals(affiliatedServiceId))
if (this.CheckWinSWEnvironmentVariable && !this.ServiceId.Equals(affiliatedServiceId))
{
Logger.Warn("The process " + pid + " has been started by Windows service with ID='" + affiliatedServiceId + "'. "
+ "It is another service (current service id is '" + ServiceId + "'), hence the process won't be terminated.");
+ "It is another service (current service id is '" + this.ServiceId + "'), hence the process won't be terminated.");
return;
}
@ -258,7 +258,7 @@ namespace winsw.Plugins.RunawayProcessKiller
StringBuilder bldr = new StringBuilder("Stopping the runaway process (pid=");
bldr.Append(pid);
bldr.Append(") and its children. Environment was ");
if (!CheckWinSWEnvironmentVariable)
if (!this.CheckWinSWEnvironmentVariable)
{
bldr.Append("not ");
}
@ -275,17 +275,16 @@ namespace winsw.Plugins.RunawayProcessKiller
/// <summary>
/// Records the started process PID for the future use in OnStart() after the restart.
/// </summary>
/// <param name="process"></param>
public override void OnProcessStarted(Process process)
{
Logger.Info("Recording PID of the started process:" + process.Id + ". PID file destination is " + Pidfile);
Logger.Info("Recording PID of the started process:" + process.Id + ". PID file destination is " + this.Pidfile);
try
{
File.WriteAllText(Pidfile, process.Id.ToString());
File.WriteAllText(this.Pidfile, process.Id.ToString());
}
catch (Exception ex)
{
Logger.Error("Cannot update the PID file " + Pidfile, ex);
Logger.Error("Cannot update the PID file " + this.Pidfile, ex);
}
}
}

View File

@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
namespace winsw.Plugins.SharedDirectoryMapper
namespace WinSW.Plugins.SharedDirectoryMapper
{
internal static class NativeMethods
{

View File

@ -2,15 +2,15 @@
using System.ComponentModel;
using System.Xml;
using log4net;
using winsw.Extensions;
using winsw.Util;
using static winsw.Plugins.SharedDirectoryMapper.NativeMethods;
using WinSW.Extensions;
using WinSW.Util;
using static WinSW.Plugins.SharedDirectoryMapper.NativeMethods;
namespace winsw.Plugins.SharedDirectoryMapper
namespace WinSW.Plugins.SharedDirectoryMapper
{
public class SharedDirectoryMapper : AbstractWinSWExtension
{
private readonly List<SharedDirectoryMapperConfig> _entries = new List<SharedDirectoryMapperConfig>();
private readonly List<SharedDirectoryMapperConfig> entries = new List<SharedDirectoryMapperConfig>();
public override string DisplayName => "Shared Directory Mapper";
@ -23,7 +23,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
public SharedDirectoryMapper(bool enableMapping, string directoryUNC, string driveLabel)
{
SharedDirectoryMapperConfig config = new SharedDirectoryMapperConfig(enableMapping, driveLabel, directoryUNC);
this._entries.Add(config);
this.entries.Add(config);
}
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
@ -36,7 +36,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
if (mapNodes[i] is XmlElement mapElement)
{
var config = SharedDirectoryMapperConfig.FromXml(mapElement);
this._entries.Add(config);
this.entries.Add(config);
}
}
}
@ -44,7 +44,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
public override void OnWrapperStarted()
{
foreach (SharedDirectoryMapperConfig config in this._entries)
foreach (SharedDirectoryMapperConfig config in this.entries)
{
string label = config.Label;
string uncPath = config.UNCPath;
@ -72,7 +72,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
public override void BeforeWrapperStopped()
{
foreach (SharedDirectoryMapperConfig config in this._entries)
foreach (SharedDirectoryMapperConfig config in this.entries)
{
string label = config.Label;
if (config.EnableMapping)

View File

@ -1,22 +1,24 @@
using System.Xml;
using winsw.Util;
using WinSW.Util;
namespace winsw.Plugins.SharedDirectoryMapper
namespace WinSW.Plugins.SharedDirectoryMapper
{
/// <summary>
/// Stores configuration entries for SharedDirectoryMapper extension.
/// </summary>
public class SharedDirectoryMapperConfig
{
public bool EnableMapping { get; set; }
public string Label { get; set; }
public string UNCPath { get; set; }
public bool EnableMapping { get; }
public string Label { get; }
public string UNCPath { get; }
public SharedDirectoryMapperConfig(bool enableMapping, string label, string uncPath)
{
EnableMapping = enableMapping;
Label = label;
UNCPath = uncPath;
this.EnableMapping = enableMapping;
this.Label = label;
this.UNCPath = uncPath;
}
public static SharedDirectoryMapperConfig FromXml(XmlElement node)

View File

@ -1,8 +1,7 @@
using System;
using winsw;
using Xunit;
namespace winswTests
namespace WinSW.Tests
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal sealed class ElevatedFactAttribute : FactAttribute

View File

@ -1,11 +1,10 @@
using System;
using System.IO;
using System.Xml;
using winsw;
using winswTests.Util;
using WinSW.Tests.Util;
using Xunit;
namespace winswTests.Configuration
namespace WinSW.Tests.Configuration
{
/// <summary>
/// Tests example configuration files.

View File

@ -1,9 +1,8 @@
using System.IO;
using winsw;
using winswTests.Util;
using WinSW.Tests.Util;
using Xunit;
namespace winswTests
namespace WinSW.Tests
{
public class DownloadConfigTests
{
@ -15,14 +14,14 @@ namespace winswTests
{
// Roundtrip data
Download d = new Download(From, To);
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd);
var loaded = this.GetSingleEntry(sd);
// Check default values
Assert.False(loaded.FailOnError);
Assert.Equal(Download.AuthType.none, loaded.Auth);
Assert.Equal(Download.AuthType.None, loaded.Auth);
Assert.Null(loaded.Username);
Assert.Null(loaded.Password);
Assert.False(loaded.UnsecureAuth);
@ -32,15 +31,15 @@ namespace winswTests
public void Roundtrip_BasicAuth()
{
// Roundtrip data
Download d = new Download(From, To, true, Download.AuthType.basic, "aUser", "aPassword", true);
var sd = ConfigXmlBuilder.create()
Download d = new Download(From, To, true, Download.AuthType.Basic, "aUser", "aPassword", true);
var sd = ConfigXmlBuilder.Create()
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd);
var loaded = this.GetSingleEntry(sd);
// Check default values
Assert.True(loaded.FailOnError);
Assert.Equal(Download.AuthType.basic, loaded.Auth);
Assert.Equal(Download.AuthType.Basic, loaded.Auth);
Assert.Equal("aUser", loaded.Username);
Assert.Equal("aPassword", loaded.Password);
Assert.True(loaded.UnsecureAuth);
@ -50,15 +49,15 @@ namespace winswTests
public void Roundtrip_SSPI()
{
// Roundtrip data
Download d = new Download(From, To, false, Download.AuthType.sspi);
var sd = ConfigXmlBuilder.create()
Download d = new Download(From, To, false, Download.AuthType.Sspi);
var sd = ConfigXmlBuilder.Create()
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd);
var loaded = this.GetSingleEntry(sd);
// Check default values
Assert.False(loaded.FailOnError);
Assert.Equal(Download.AuthType.sspi, loaded.Auth);
Assert.Equal(Download.AuthType.Sspi, loaded.Auth);
Assert.Null(loaded.Username);
Assert.Null(loaded.Password);
Assert.False(loaded.UnsecureAuth);
@ -72,22 +71,22 @@ namespace winswTests
public void RejectBasicAuth_With_UnsecureProtocol(string protocolPrefix)
{
string unsecureFrom = protocolPrefix + "myServer.com:8080/file.txt";
var d = new Download(unsecureFrom, To, auth: Download.AuthType.basic, username: "aUser", password: "aPassword");
AssertInitializationFails(d, "Warning: you're sending your credentials in clear text to the server");
var d = new Download(unsecureFrom, To, auth: Download.AuthType.Basic, username: "aUser", password: "aPassword");
this.AssertInitializationFails(d, "Warning: you're sending your credentials in clear text to the server");
}
[Fact]
public void RejectBasicAuth_Without_Username()
{
var d = new Download(From, To, auth: Download.AuthType.basic, username: null, password: "aPassword");
AssertInitializationFails(d, "Basic Auth is enabled, but username is not specified");
var d = new Download(From, To, auth: Download.AuthType.Basic, username: null, password: "aPassword");
this.AssertInitializationFails(d, "Basic Auth is enabled, but username is not specified");
}
[Fact]
public void RejectBasicAuth_Without_Password()
{
var d = new Download(From, To, auth: Download.AuthType.basic, username: "aUser", password: null);
AssertInitializationFails(d, "Basic Auth is enabled, but password is not specified");
var d = new Download(From, To, auth: Download.AuthType.Basic, username: "aUser", password: null);
this.AssertInitializationFails(d, "Basic Auth is enabled, but password is not specified");
}
/// <summary>
@ -100,11 +99,11 @@ namespace winswTests
{
Download d = new Download(From, To, failOnError);
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd);
var loaded = this.GetSingleEntry(sd);
Assert.Equal(From, loaded.From);
Assert.Equal(To, loaded.To);
Assert.Equal(failOnError, loaded.FailOnError);
@ -116,11 +115,11 @@ namespace winswTests
[Fact]
public void Download_FailOnError_Undefined()
{
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\"/>")
.ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd);
var loaded = this.GetSingleEntry(sd);
Assert.False(loaded.FailOnError);
}
@ -131,22 +130,22 @@ namespace winswTests
[InlineData("Sspi")]
public void AuthType_Is_CaseInsensitive(string authType)
{
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"" + authType + "\"/>")
.ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd);
Assert.Equal(Download.AuthType.sspi, loaded.Auth);
var loaded = this.GetSingleEntry(sd);
Assert.Equal(Download.AuthType.Sspi, loaded.Auth);
}
[Fact]
public void Should_Fail_On_Unsupported_AuthType()
{
// TODO: will need refactoring once all fields are being parsed on startup
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"digest\"/>")
.ToServiceDescriptor(true);
var e = Assert.Throws<InvalidDataException>(() => GetSingleEntry(sd));
var e = Assert.Throws<InvalidDataException>(() => this.GetSingleEntry(sd));
Assert.StartsWith("Cannot parse <auth> Enum value from string 'digest'", e.Message);
}
@ -188,11 +187,11 @@ namespace winswTests
private void AssertInitializationFails(Download download, string expectedMessagePart = null)
{
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithDownload(download)
.ToServiceDescriptor(true);
var e = Assert.Throws<InvalidDataException>(() => GetSingleEntry(sd));
var e = Assert.Throws<InvalidDataException>(() => this.GetSingleEntry(sd));
Assert.StartsWith(expectedMessagePart, e.Message);
}
}

View File

@ -4,11 +4,10 @@ using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using winsw;
using winswTests.Util;
using WinSW.Tests.Util;
using Xunit;
namespace winswTests
namespace WinSW.Tests
{
public class DownloadTests : IDisposable
{
@ -107,7 +106,7 @@ namespace winswTests
await this.TestClientServerAsync(
async (source, dest) =>
{
await new Download(source, dest, false, Download.AuthType.none).PerformAsync();
await new Download(source, dest, false, Download.AuthType.None).PerformAsync();
Assert.Equal(this.contents, File.ReadAllBytes(dest));
},
context =>
@ -132,7 +131,7 @@ namespace winswTests
await this.TestClientServerAsync(
async (source, dest) =>
{
await new Download(source, dest, false, Download.AuthType.basic, username, password, true).PerformAsync();
await new Download(source, dest, false, Download.AuthType.Basic, username, password, true).PerformAsync();
Assert.Equal(this.contents, File.ReadAllBytes(dest));
},
context =>

View File

@ -1,6 +1,6 @@
using System;
namespace winswTests.Extensions
namespace WinSW.Tests.Extensions
{
/// <summary>
/// Base class for testing of WinSW Extensions.

View File

@ -1,20 +1,19 @@
using System;
using System.Diagnostics;
using System.IO;
using winsw;
using winsw.Extensions;
using winsw.Plugins.RunawayProcessKiller;
using winsw.Util;
using winswTests.Util;
using WinSW.Extensions;
using WinSW.Plugins.RunawayProcessKiller;
using WinSW.Tests.Util;
using WinSW.Util;
using Xunit;
namespace winswTests.Extensions
namespace WinSW.Tests.Extensions
{
public class RunawayProcessKillerExtensionTest : ExtensionTestBase
{
readonly ServiceDescriptor _testServiceDescriptor;
private readonly ServiceDescriptor testServiceDescriptor;
readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension));
private readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension));
public RunawayProcessKillerExtensionTest()
{
@ -27,19 +26,19 @@ $@"<service>
<arguments>-Xrs -jar \""%BASE%\slave.jar\"" -jnlpUrl ...</arguments>
<log mode=""roll""></log>
<extensions>
<extension enabled=""true"" className=""{testExtension}"" id=""killRunawayProcess"">
<extension enabled=""true"" className=""{this.testExtension}"" id=""killRunawayProcess"">
<pidfile>foo/bar/pid.txt</pidfile>
<stopTimeout>5000</stopTimeout>
</extension>
</extensions>
</service>";
_testServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
this.testServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
}
[Fact]
public void LoadExtensions()
{
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
manager.LoadExtensions();
_ = Assert.Single(manager.Extensions);
@ -53,7 +52,7 @@ $@"<service>
[Fact]
public void StartStopExtension()
{
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
manager.LoadExtensions();
manager.FireOnWrapperStarted();
manager.FireBeforeWrapperStopped();
@ -72,14 +71,14 @@ $@"<service>
ps.Arguments = "/c pause";
ps.UseShellExecute = false;
ps.RedirectStandardOutput = true;
ps.EnvironmentVariables[WinSWSystem.ENVVAR_NAME_SERVICE_ID] = winswId;
ps.EnvironmentVariables[WinSWSystem.EnvVarNameServiceId] = winswId;
proc.Start();
try
{
// Generate extension and ensure that the roundtrip is correct
var pidfile = Path.Combine(tmpDir, "process.pid");
var sd = ConfigXmlBuilder.create(id: winswId)
var sd = ConfigXmlBuilder.Create(id: winswId)
.WithRunawayProcessKiller(new RunawayProcessKillerExtension(pidfile), extensionId)
.ToServiceDescriptor();
WinSWExtensionManager manager = new WinSWExtensionManager(sd);

View File

@ -1,15 +1,14 @@
using winsw;
using winsw.Extensions;
using winsw.Plugins.SharedDirectoryMapper;
using WinSW.Extensions;
using WinSW.Plugins.SharedDirectoryMapper;
using Xunit;
namespace winswTests.Extensions
namespace WinSW.Tests.Extensions
{
public class SharedDirectoryMapperConfigTest : ExtensionTestBase
{
readonly ServiceDescriptor _testServiceDescriptor;
private readonly ServiceDescriptor testServiceDescriptor;
readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(SharedDirectoryMapper));
private readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(SharedDirectoryMapper));
public SharedDirectoryMapperConfigTest()
{
@ -22,13 +21,13 @@ $@"<service>
<arguments>-Xrs -jar \""%BASE%\slave.jar\"" -jnlpUrl ...</arguments>
<log mode=""roll""></log>
<extensions>
<extension enabled=""true"" className=""{testExtension}"" id=""mapNetworDirs"">
<extension enabled=""true"" className=""{this.testExtension}"" id=""mapNetworDirs"">
<mapping>
<map enabled=""false"" label=""N:"" uncpath=""\\UNC""/>
<map enabled=""false"" label=""M:"" uncpath=""\\UNC2""/>
</mapping>
</extension>
<extension enabled=""true"" className=""{testExtension}"" id=""mapNetworDirs2"">
<extension enabled=""true"" className=""{this.testExtension}"" id=""mapNetworDirs2"">
<mapping>
<map enabled=""false"" label=""X:"" uncpath=""\\UNC""/>
<map enabled=""false"" label=""Y:"" uncpath=""\\UNC2""/>
@ -36,13 +35,13 @@ $@"<service>
</extension>
</extensions>
</service>";
_testServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
this.testServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
}
[Fact]
public void LoadExtensions()
{
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
manager.LoadExtensions();
Assert.Equal(2, manager.Extensions.Count);
}
@ -50,7 +49,7 @@ $@"<service>
[Fact]
public void StartStopExtension()
{
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
manager.LoadExtensions();
manager.FireOnWrapperStarted();
manager.FireBeforeWrapperStopped();

View File

@ -3,10 +3,10 @@ using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using winsw.Plugins.SharedDirectoryMapper;
using WinSW.Plugins.SharedDirectoryMapper;
using Xunit;
namespace winswTests.Extensions
namespace WinSW.Tests.Extensions
{
// TODO: Assert.Throws<ExtensionException>
public class SharedDirectoryMapperTests

View File

@ -1,10 +1,9 @@
using System;
using System.ServiceProcess;
using winsw;
using winswTests.Util;
using WinSW.Tests.Util;
using Xunit;
namespace winswTests
namespace WinSW.Tests
{
public class MainTest
{
@ -47,6 +46,7 @@ namespace winswTests
Assert.Contains("start", cliOut);
Assert.Contains("help", cliOut);
Assert.Contains("version", cliOut);
// TODO: check all commands after the migration of ccommands to enum
}

View File

@ -1,22 +1,21 @@
using System;
using System.Diagnostics;
using System.ServiceProcess;
using winsw;
using winswTests.Util;
using WinSW.Tests.Util;
using Xunit;
namespace winswTests
namespace WinSW.Tests
{
public class ServiceDescriptorTests
{
private ServiceDescriptor _extendedServiceDescriptor;
private const string ExpectedWorkingDirectory = @"Z:\Path\SubPath";
private const string Username = "User";
private const string Password = "Password";
private const string Domain = "Domain";
private const string AllowServiceAccountLogonRight = "true";
private ServiceDescriptor extendedServiceDescriptor;
public ServiceDescriptorTests()
{
string seedXml =
@ -35,13 +34,13 @@ $@"<service>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
}
[Fact]
public void DefaultStartMode()
{
Assert.Equal(ServiceStartMode.Automatic, _extendedServiceDescriptor.StartMode);
Assert.Equal(ServiceStartMode.Automatic, this.extendedServiceDescriptor.StartMode);
}
[Fact]
@ -65,8 +64,8 @@ $@"<service>
<logpath>C:\logs</logpath>
</service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.Throws<ArgumentException>(() => _extendedServiceDescriptor.StartMode);
this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Throws<ArgumentException>(() => this.extendedServiceDescriptor.StartMode);
}
[Fact]
@ -90,47 +89,47 @@ $@"<service>
<logpath>C:\logs</logpath>
</service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.Equal(ServiceStartMode.Manual, _extendedServiceDescriptor.StartMode);
this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(ServiceStartMode.Manual, this.extendedServiceDescriptor.StartMode);
}
[Fact]
public void VerifyWorkingDirectory()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(ExpectedWorkingDirectory, _extendedServiceDescriptor.WorkingDirectory);
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(ExpectedWorkingDirectory, this.extendedServiceDescriptor.WorkingDirectory);
}
[Fact]
public void VerifyServiceLogonRight()
{
Assert.True(_extendedServiceDescriptor.AllowServiceAcountLogonRight);
Assert.True(this.extendedServiceDescriptor.AllowServiceAcountLogonRight);
}
[Fact]
public void VerifyUsername()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(Domain + "\\" + Username, _extendedServiceDescriptor.ServiceAccountUserName);
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(Domain + "\\" + Username, this.extendedServiceDescriptor.ServiceAccountUserName);
}
[Fact]
public void VerifyPassword()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(Password, _extendedServiceDescriptor.ServiceAccountPassword);
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(Password, this.extendedServiceDescriptor.ServiceAccountPassword);
}
[Fact]
public void Priority()
{
var sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>normal</priority></service>");
var sd = ServiceDescriptor.FromXml("<service><id>test</id><priority>normal</priority></service>");
Assert.Equal(ProcessPriorityClass.Normal, sd.Priority);
sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>idle</priority></service>");
sd = ServiceDescriptor.FromXml("<service><id>test</id><priority>idle</priority></service>");
Assert.Equal(ProcessPriorityClass.Idle, sd.Priority);
sd = ServiceDescriptor.FromXML("<service><id>test</id></service>");
sd = ServiceDescriptor.FromXml("<service><id>test</id></service>");
Assert.Equal(ProcessPriorityClass.Normal, sd.Priority);
}
@ -140,7 +139,7 @@ $@"<service>
const string seedXml = "<service>"
+ "<stoptimeout>60sec</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(TimeSpan.FromSeconds(60), serviceDescriptor.StopTimeout);
}
@ -151,7 +150,7 @@ $@"<service>
const string seedXml = "<service>"
+ "<stoptimeout>10min</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(TimeSpan.FromMinutes(10), serviceDescriptor.StopTimeout);
}
@ -162,7 +161,7 @@ $@"<service>
const string seedXml = "<service>"
+ "<logname>MyTestApp</logname>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal("MyTestApp", serviceDescriptor.LogName);
}
@ -173,7 +172,7 @@ $@"<service>
const string seedXml = "<service>"
+ "<outfiledisabled>true</outfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.True(serviceDescriptor.OutFileDisabled);
}
@ -184,7 +183,7 @@ $@"<service>
const string seedXml = "<service>"
+ "<errfiledisabled>true</errfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.True(serviceDescriptor.ErrFileDisabled);
}
@ -195,7 +194,7 @@ $@"<service>
const string seedXml = "<service>"
+ "<outfilepattern>.out.test.log</outfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(".out.test.log", serviceDescriptor.OutFilePattern);
}
@ -206,7 +205,7 @@ $@"<service>
const string seedXml = "<service>"
+ "<errfilepattern>.err.test.log</errfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(".err.test.log", serviceDescriptor.ErrFilePattern);
}
@ -222,12 +221,12 @@ $@"<service>
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as SizeBasedRollingLogAppender;
Assert.NotNull(logHandler);
Assert.Equal(112 * 1024, logHandler.SizeTheshold);
Assert.Equal(112 * 1024, logHandler.SizeThreshold);
Assert.Equal(113, logHandler.FilesToKeep);
}
@ -242,7 +241,7 @@ $@"<service>
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as TimeBasedRollingLogAppender;
@ -263,12 +262,12 @@ $@"<service>
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as RollingSizeTimeLogAppender;
Assert.NotNull(logHandler);
Assert.Equal(10240 * 1024, logHandler.SizeTheshold);
Assert.Equal(10240 * 1024, logHandler.SizeThreshold);
Assert.Equal("yyyy-MM-dd", logHandler.FilePattern);
Assert.Equal((TimeSpan?)new TimeSpan(0, 0, 0), logHandler.AutoRollAtTime);
}
@ -284,7 +283,7 @@ $@"<service>
+ "<allowservicelogon>true1</allowservicelogon>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.False(serviceDescriptor.AllowServiceAcountLogonRight);
}
@ -298,14 +297,14 @@ $@"<service>
+ "<password>" + Password + "</password>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.False(serviceDescriptor.AllowServiceAcountLogonRight);
}
[Fact]
public void VerifyWaitHint_FullXML()
{
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithTag("waithint", "20 min")
.ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromMinutes(20), sd.WaitHint);
@ -317,7 +316,7 @@ $@"<service>
[Fact]
public void VerifyWaitHint_XMLWithoutVersion()
{
var sd = ConfigXmlBuilder.create(printXMLVersion: false)
var sd = ConfigXmlBuilder.Create(printXMLVersion: false)
.WithTag("waithint", "21 min")
.ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromMinutes(21), sd.WaitHint);
@ -326,7 +325,7 @@ $@"<service>
[Fact]
public void VerifyWaitHint_XMLWithoutComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null)
var sd = ConfigXmlBuilder.Create(xmlComment: null)
.WithTag("waithint", "22 min")
.ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromMinutes(22), sd.WaitHint);
@ -335,7 +334,7 @@ $@"<service>
[Fact]
public void VerifyWaitHint_XMLWithoutVersionAndComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null, printXMLVersion: false)
var sd = ConfigXmlBuilder.Create(xmlComment: null, printXMLVersion: false)
.WithTag("waithint", "23 min")
.ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromMinutes(23), sd.WaitHint);
@ -344,21 +343,21 @@ $@"<service>
[Fact]
public void VerifySleepTime()
{
var sd = ConfigXmlBuilder.create().WithTag("sleeptime", "3 hrs").ToServiceDescriptor(true);
var sd = ConfigXmlBuilder.Create().WithTag("sleeptime", "3 hrs").ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromHours(3), sd.SleepTime);
}
[Fact]
public void VerifyResetFailureAfter()
{
var sd = ConfigXmlBuilder.create().WithTag("resetfailure", "75 sec").ToServiceDescriptor(true);
var sd = ConfigXmlBuilder.Create().WithTag("resetfailure", "75 sec").ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromSeconds(75), sd.ResetFailureAfter);
}
[Fact]
public void VerifyStopTimeout()
{
var sd = ConfigXmlBuilder.create().WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true);
var sd = ConfigXmlBuilder.Create().WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromSeconds(35), sd.StopTimeout);
}
@ -368,14 +367,14 @@ $@"<service>
[Fact]
public void Arguments_LegacyParam()
{
var sd = ConfigXmlBuilder.create().WithTag("arguments", "arg").ToServiceDescriptor(true);
var sd = ConfigXmlBuilder.Create().WithTag("arguments", "arg").ToServiceDescriptor(true);
Assert.Equal("arg", sd.Arguments);
}
[Fact]
public void Arguments_NewParam_Single()
{
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithTag("argument", "--arg1=2")
.ToServiceDescriptor(true);
Assert.Equal(" --arg1=2", sd.Arguments);
@ -384,7 +383,7 @@ $@"<service>
[Fact]
public void Arguments_NewParam_MultipleArgs()
{
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithTag("argument", "--arg1=2")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
@ -398,7 +397,7 @@ $@"<service>
[Fact]
public void Arguments_Bothparam_Priorities()
{
var sd = ConfigXmlBuilder.create()
var sd = ConfigXmlBuilder.Create()
.WithTag("arguments", "--arg1=2 --arg2=3")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
@ -411,7 +410,7 @@ $@"<service>
[InlineData(false)]
public void DelayedStart_RoundTrip(bool enabled)
{
var bldr = ConfigXmlBuilder.create();
var bldr = ConfigXmlBuilder.Create();
if (enabled)
{
bldr = bldr.WithDelayedAutoStart();

View File

@ -1,9 +1,8 @@
using System;
using System.IO;
using winsw;
using Xunit;
namespace winswTests.Util
namespace WinSW.Tests.Util
{
/// <summary>
/// Helper for WinSW CLI testing
@ -25,7 +24,7 @@ $@"<service>
<logpath>C:\winsw\logs</logpath>
</service>";
public static readonly ServiceDescriptor DefaultServiceDescriptor = ServiceDescriptor.FromXML(SeedXml);
public static readonly ServiceDescriptor DefaultServiceDescriptor = ServiceDescriptor.FromXml(SeedXml);
/// <summary>
/// Runs a simle test, which returns the output CLI
@ -115,13 +114,13 @@ $@"<service>
public Exception Exception { get; }
public bool HasException => Exception != null;
public bool HasException => this.Exception != null;
public CLITestResult(string output, string error, Exception exception = null)
{
Out = output;
Error = error;
Exception = exception;
this.Out = output;
this.Error = error;
this.Exception = exception;
}
}
}

View File

@ -1,36 +1,45 @@
using System;
using System.Collections.Generic;
using System.Text;
using winsw;
using winsw.Plugins.RunawayProcessKiller;
using winswTests.Extensions;
using WinSW.Plugins.RunawayProcessKiller;
using WinSW.Tests.Extensions;
namespace winswTests.Util
namespace WinSW.Tests.Util
{
/// <summary>
/// Configuration XML builder, which simplifies testing of WinSW Configuration file.
/// </summary>
class ConfigXmlBuilder
internal class ConfigXmlBuilder
{
public string Name { get; set; }
public string Id { get; set; }
public string Description { get; set; }
public string Executable { get; set; }
public bool PrintXMLVersion { get; set; }
public string XMLComment { get; set; }
public List<string> ExtensionXmls { get; private set; }
public bool PrintXmlVersion { get; set; }
public string XmlComment { get; set; }
public List<string> ExtensionXmls { get; }
private readonly List<string> configEntries;
// TODO: Switch to the initializer?
private ConfigXmlBuilder()
{
configEntries = new List<string>();
ExtensionXmls = new List<string>();
this.configEntries = new List<string>();
this.ExtensionXmls = new List<string>();
}
public static ConfigXmlBuilder create(string id = null, string name = null,
string description = null, string executable = null, bool printXMLVersion = true,
public static ConfigXmlBuilder Create(
string id = null,
string name = null,
string description = null,
string executable = null,
bool printXMLVersion = true,
string xmlComment = "")
{
var config = new ConfigXmlBuilder();
@ -38,43 +47,43 @@ namespace winswTests.Util
config.Name = name ?? "MyApp Service";
config.Description = description ?? "MyApp Service (powered by WinSW)";
config.Executable = executable ?? "%BASE%\\myExecutable.exe";
config.PrintXMLVersion = printXMLVersion;
config.XMLComment = (xmlComment != null && xmlComment.Length == 0)
config.PrintXmlVersion = printXMLVersion;
config.XmlComment = (xmlComment != null && xmlComment.Length == 0)
? "Just a sample configuration file generated by the test suite"
: xmlComment;
return config;
}
public string ToXMLString(bool dumpConfig = false)
public string ToXmlString(bool dumpConfig = false)
{
StringBuilder str = new StringBuilder();
if (PrintXMLVersion)
if (this.PrintXmlVersion)
{
// TODO: The encoding is generally wrong
str.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
}
if (XMLComment != null)
if (this.XmlComment != null)
{
str.AppendFormat("<!--{0}-->\n", XMLComment);
str.AppendFormat("<!--{0}-->\n", this.XmlComment);
}
str.Append("<service>\n");
str.AppendFormat(" <id>{0}</id>\n", Id);
str.AppendFormat(" <name>{0}</name>\n", Name);
str.AppendFormat(" <description>{0}</description>\n", Description);
str.AppendFormat(" <executable>{0}</executable>\n", Executable);
foreach (string entry in configEntries)
str.AppendFormat(" <id>{0}</id>\n", this.Id);
str.AppendFormat(" <name>{0}</name>\n", this.Name);
str.AppendFormat(" <description>{0}</description>\n", this.Description);
str.AppendFormat(" <executable>{0}</executable>\n", this.Executable);
foreach (string entry in this.configEntries)
{
// We do not care much about pretty formatting here
str.AppendFormat(" {0}\n", entry);
}
// Extensions
if (ExtensionXmls.Count > 0)
if (this.ExtensionXmls.Count > 0)
{
str.Append(" <extensions>\n");
foreach (string xml in ExtensionXmls)
foreach (string xml in this.ExtensionXmls)
{
str.Append(xml);
}
@ -95,18 +104,18 @@ namespace winswTests.Util
public ServiceDescriptor ToServiceDescriptor(bool dumpConfig = false)
{
return ServiceDescriptor.FromXML(ToXMLString(dumpConfig));
return ServiceDescriptor.FromXml(this.ToXmlString(dumpConfig));
}
public ConfigXmlBuilder WithRawEntry(string entry)
{
configEntries.Add(entry);
this.configEntries.Add(entry);
return this;
}
public ConfigXmlBuilder WithTag(string tagName, string value)
{
return WithRawEntry(string.Format("<{0}>{1}</{0}>", tagName, value));
return this.WithRawEntry(string.Format("<{0}>{1}</{0}>", tagName, value));
}
public ConfigXmlBuilder WithRunawayProcessKiller(RunawayProcessKillerExtension ext, string extensionId = "killRunawayProcess", bool enabled = true)
@ -118,7 +127,7 @@ namespace winswTests.Util
str.AppendFormat(" <stopTimeout>{0}</stopTimeout>\n", ext.StopTimeout.TotalMilliseconds);
str.AppendFormat(" <checkWinSWEnvironmentVariable>{0}</checkWinSWEnvironmentVariable>\n", ext.CheckWinSWEnvironmentVariable);
str.Append(" </extension>\n");
ExtensionXmls.Add(str.ToString());
this.ExtensionXmls.Add(str.ToString());
return this;
}
@ -129,10 +138,10 @@ namespace winswTests.Util
xml.Append($"<download from=\"{download.From}\" to=\"{download.To}\" failOnError=\"{download.FailOnError}\"");
// Authentication
if (download.Auth != Download.AuthType.none)
if (download.Auth != Download.AuthType.None)
{
xml.Append($" auth=\"{download.Auth}\"");
if (download.Auth == Download.AuthType.basic)
if (download.Auth == Download.AuthType.Basic)
{
string username = download.Username;
if (username != null)
@ -155,12 +164,12 @@ namespace winswTests.Util
xml.Append("/>");
return WithRawEntry(xml.ToString());
return this.WithRawEntry(xml.ToString());
}
public ConfigXmlBuilder WithDelayedAutoStart()
{
return WithRawEntry("<delayedAutoStart/>");
return this.WithRawEntry("<delayedAutoStart/>");
}
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace winswTests.Util
namespace WinSW.Tests.Util
{
internal static class DateTimeExtensions
{

View File

@ -1,9 +1,9 @@
using System;
using System.IO;
namespace winswTests.Util
namespace WinSW.Tests.Util
{
class FilesystemTestHelper
internal class FilesystemTestHelper
{
/// <summary>
/// Creates a temporary directory for testing.

View File

@ -1,16 +1,14 @@
using System.Collections.Generic;
using System.Reflection;
using winsw;
using winsw.Configuration;
using WinSW.Configuration;
using Xunit;
namespace winswTests.Util
namespace WinSW.Tests.Util
{
public static class ServiceDescriptorAssert
{
// TODO: convert to Extension attributes once the .NET dependency is upgraded
// BTW there is a way to get them working in .NET2, but KISS
public static void AssertPropertyIsDefault(ServiceDescriptor desc, string property)
{
PropertyInfo actualProperty = typeof(ServiceDescriptor).GetProperty(property);

7
src/stylecop.json Normal file
View File

@ -0,0 +1,7 @@
{
"settings": {
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace"
}
}
}

View File

@ -1,12 +1,13 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30128.74
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{077C2CEC-B687-4B53-86E9-C1A1BF5554E5}"
EndProject
@ -14,12 +15,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{BC4A
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5297623A-1A95-4F89-9AAE-DA634081EC86}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunawayProcessKiller", "Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj", "{57284B7A-82A4-407A-B706-EBEA6BF8EA13}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RunawayProcessKiller", "Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj", "{57284B7A-82A4-407A-B706-EBEA6BF8EA13}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AA414F46-B863-473A-A0E0-C2971B3396AE}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
..\samples\sample-complete.xml = ..\samples\sample-complete.xml
..\samples\sample-minimal.xml = ..\samples\sample-minimal.xml
EndProjectSection
@ -83,4 +85,7 @@ Global
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06} = {5297623A-1A95-4F89-9AAE-DA634081EC86}
{57284B7A-82A4-407A-B706-EBEA6BF8EA13} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F9F8AFEA-196D-4041-8441-FABC3B730F9E}
EndGlobalSection
EndGlobal

View File

@ -1,3 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SW/@EntryIndexedValue">SW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UNC/@EntryIndexedValue">UNC</s:String></wpf:ResourceDictionary>