Standardize coding styles

pull/576/head
NextTurn 2020-07-08 00:00:00 +08:00 committed by Next Turn
parent d2e33f1749
commit e116368fc4
61 changed files with 820 additions and 608 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>

64
src/.editorconfig Normal file
View File

@ -0,0 +1,64 @@
[*.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
# 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
# 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

@ -18,13 +18,13 @@ using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using winsw.Logging;
using winsw.Native;
using winsw.Util;
using WinSW.Logging;
using WinSW.Native;
using WinSW.Util;
using WMI;
using ServiceType = WMI.ServiceType;
namespace winsw
namespace WinSW
{
public static class Program
{
@ -86,8 +86,8 @@ namespace winsw
}
// Get service info for the future use
Win32Services svcs = new WmiRoot().GetCollection<Win32Services>();
Win32Service? svc = svcs.Select(descriptor.Id);
IWin32Services svcs = new WmiRoot().GetCollection<IWin32Services>();
IWin32Service? svc = svcs.Select(descriptor.Id);
var args = new List<string>(Array.AsReadOnly(argsArray));
if (args[0] == "/redirect")
@ -452,7 +452,6 @@ namespace winsw
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
// run restart from another process group. see README.md for why this is useful.
bool result = ProcessApis.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, ProcessApis.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _);
if (!result)
{
@ -537,6 +536,7 @@ namespace winsw
{
// TODO: Make logging levels configurable
Level fileLogLevel = Level.Debug;
// TODO: Debug should not be printed to console by default. Otherwise commands like 'status' will be pollutted
// This is a workaround till there is a better command line parsing, which will allow determining
Level consoleLogLevel = Level.Info;
@ -581,7 +581,7 @@ namespace winsw
{
Name = "Wrapper event log",
Threshold = eventLogLevel,
provider = WrapperService.eventLogProvider,
Provider = WrapperService.eventLogProvider,
};
systemEventLogger.ActivateOptions();
appenders.Add(systemEventLogger);

View File

@ -10,20 +10,20 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
#endif
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; }
@ -32,14 +32,15 @@ namespace winsw
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
@ -52,24 +53,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())
{
}
@ -79,9 +81,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
{
@ -89,15 +93,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
@ -117,7 +121,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);
}
}
@ -127,21 +131,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. */
}
@ -149,7 +153,7 @@ namespace winsw
{
try
{
EventLog.WriteEntry(message);
this.EventLog.WriteEntry(message);
}
catch (Exception e)
{
@ -160,7 +164,7 @@ namespace winsw
public void LogEvent(string message, EventLogEntryType type)
{
if (_systemShuttingdown)
if (this.systemShuttingdown)
{
/* NOP - cannot call EventLog because of shutdown. */
}
@ -168,7 +172,7 @@ namespace winsw
{
try
{
EventLog.WriteEntry(message, type);
this.EventLog.WriteEntry(message, type);
}
catch (Exception e)
{
@ -179,7 +183,8 @@ namespace winsw
protected override void OnStart(string[] args)
{
_envs = _descriptor.EnvironmentVariables;
this.envs = this.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
/**
@ -187,17 +192,17 @@ namespace winsw
{
LogEvent("envar " + key + '=' + _envs[key]);
}*/
HandleFileCopies();
this.HandleFileCopies();
// handle downloads
#if VNEXT
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();
}
@ -216,7 +221,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
@ -230,10 +235,10 @@ namespace winsw
throw new AggregateException(exceptions);
}
#else
foreach (Download download in _descriptor.Downloads)
foreach (Download download in this.descriptor.Downloads)
{
string downloadMessage = $"Downloading: {download.From} to {download.To}. failOnError={download.FailOnError.ToString()}";
LogEvent(downloadMessage);
this.LogEvent(downloadMessage);
Log.Info(downloadMessage);
try
{
@ -242,7 +247,7 @@ namespace winsw
catch (Exception e)
{
string errorMessage = $"Failed to download {download.From} to {download.To}";
LogEvent($"{errorMessage}. {e.Message}");
this.LogEvent($"{errorMessage}. {e.Message}");
Log.Error(errorMessage, e);
// TODO: move this code into the download logic
@ -256,34 +261,34 @@ namespace winsw
}
#endif
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()
@ -292,8 +297,8 @@ namespace winsw
try
{
_systemShuttingdown = true;
StopIt();
this.systemShuttingdown = true;
this.StopIt();
}
catch (Exception ex)
{
@ -307,7 +312,7 @@ namespace winsw
try
{
StopIt();
this.StopIt();
}
catch (Exception ex)
{
@ -324,18 +329,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.StopProcessAndChildren(_process.Id, _descriptor.StopTimeout, _descriptor.StopParentProcessFirst);
ExtensionManager.FireOnProcessTerminated(_process);
Log.Debug("ProcessKill " + this.process.Id);
ProcessHelper.StopProcessAndChildren(this.process.Id, this.descriptor.StopTimeout, this.descriptor.StopParentProcessFirst);
this.ExtensionManager.FireOnProcessTerminated(this.process);
}
catch (InvalidOperationException)
{
@ -344,48 +349,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
@ -394,7 +399,7 @@ namespace winsw
while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
{
SignalShutdownPending();
this.SignalShutdownPending();
// WriteEvent("WaitForProcessToExit [repeat]");
}
}
@ -409,27 +414,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 = ServiceApis.ServiceState.STOPPED;
ServiceApis.SetServiceStatus(handle, _wrapperServiceStatus);
this.wrapperServiceStatus.CurrentState = ServiceApis.ServiceState.STOPPED;
ServiceApis.SetServiceStatus(handle, this.wrapperServiceStatus);
}
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler, bool redirectStdin)
@ -440,25 +445,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
{
@ -471,13 +479,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,57 +5,82 @@ using System.IO;
using System.Xml;
using WMI;
namespace winsw.Configuration
namespace WinSW.Configuration
{
/// <summary>
/// Default WinSW settings
/// 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? ServiceAccountUser => 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);
public bool StopParentProcessFirst => false;
// Service management
public StartMode StartMode => StartMode.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,50 +4,71 @@ using System.Diagnostics;
using System.Xml;
using WMI;
namespace winsw.Configuration
namespace WinSW.Configuration
{
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? ServiceAccountUser { 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; }
bool StopParentProcessFirst { get; }
// Service management
StartMode 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

@ -10,9 +10,9 @@ using System.Threading.Tasks;
#endif
using System.Xml;
using log4net;
using winsw.Util;
using WinSW.Util;
namespace winsw
namespace WinSW
{
/// <summary>
/// Specify the download activities prior to the launch.
@ -22,9 +22,9 @@ namespace winsw
{
public enum AuthType
{
none = 0,
sspi,
basic
None = 0,
Sspi,
Basic
}
private static readonly ILog Logger = LogManager.GetLogger(typeof(Download));
@ -38,7 +38,7 @@ namespace winsw
public readonly bool FailOnError;
public readonly string? Proxy;
public string ShortId => $"(download from {From})";
public string ShortId => $"(download from {this.From})";
static Download()
{
@ -73,59 +73,59 @@ 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>
/// Constructs the download setting sfrom the XML entry
/// </summary>
/// <param name="n">XML element</param>
/// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid</exception>
/// <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);
}
}
}
@ -150,10 +150,10 @@ namespace winsw
public void Perform()
#endif
{
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);
@ -164,35 +164,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
{
#if VNEXT
@ -217,18 +217,18 @@ namespace winsw
#endif
}
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
{
@ -253,6 +253,7 @@ namespace winsw
public class CustomProxyInformation
{
public string ServerAddress { get; set; }
public NetworkCredential? Credentials { get; set; }
public CustomProxyInformation(string proxy)
@ -267,12 +268,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 + "@", string.Empty);
}
else
{
ServerAddress = proxy;
this.ServerAddress = proxy;
}
}
}

View File

@ -80,14 +80,19 @@ namespace DynamicProxy
// Load "this"
constructorIL.Emit(OpCodes.Ldarg_0);
// Load first constructor parameter
constructorIL.Emit(OpCodes.Ldarg_1);
// Set the first parameter into the handler field
constructorIL.Emit(OpCodes.Stfld, handlerField);
// Load "this"
constructorIL.Emit(OpCodes.Ldarg_0);
// Call the super constructor
constructorIL.Emit(OpCodes.Call, baseConstructor);
// Constructor return
constructorIL.Emit(OpCodes.Ret);
@ -136,7 +141,8 @@ namespace DynamicProxy
methodInfo.Name,
/*MethodAttributes.Public | MethodAttributes.Virtual | */ methodInfo.Attributes & ~MethodAttributes.Abstract,
CallingConventions.Standard,
methodInfo.ReturnType, methodParameters);
methodInfo.ReturnType,
methodParameters);
ILGenerator methodIL = methodBuilder.GetILGenerator();

View File

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

View File

@ -1,6 +1,6 @@
using System;
namespace winsw.Extensions
namespace WinSW.Extensions
{
public class ExtensionException : WinSWException
{
@ -9,15 +9,15 @@ namespace winsw.Extensions
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,4 +1,4 @@
namespace winsw.Extensions
namespace WinSW.Extensions
{
/// <summary>
/// This attribute is used to identify extension points within the code
@ -6,7 +6,7 @@
/// <remarks>
/// Each extension point implements its own entry type.
/// </remarks>
class ExtensionPointAttribute
internal 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"/>
@ -28,9 +28,9 @@ namespace winsw.Extensions
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,7 +3,7 @@ using System.Collections.Generic;
using System.Xml;
using log4net;
namespace winsw.Extensions
namespace WinSW.Extensions
{
public class WinSWExtensionManager
{
@ -15,8 +15,8 @@ namespace winsw.Extensions
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
{
@ -101,16 +101,16 @@ namespace winsw.Extensions
// TODO: Implement loading of external extensions. Current version supports internal hack
#region Extension load management
/// <summary>
/// Loads extensions according to the configuration file.
/// </summary>
/// <param name="logger">Logger</param>
/// <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);
}
}
@ -118,16 +118,15 @@ namespace winsw.Extensions
/// Loads extensions from the configuration file
/// </summary>
/// <param name="id">Extension ID</param>
/// <param name="logger">Logger</param>
/// <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 +136,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 +148,7 @@ namespace winsw.Extensions
throw ex;
}
Extensions.Add(id, extension);
this.Extensions.Add(id, extension);
Log.Info("Extension loaded: " + id);
}
else

View File

@ -8,12 +8,11 @@ using System.Threading;
#if !VNEXT
using ICSharpCode.SharpZipLib.Zip;
#endif
using winsw.Util;
using WinSW.Util;
namespace winsw
namespace WinSW
{
// ReSharper disable once InconsistentNaming
public interface EventLogger
public interface IEventLogger
{
void LogEvent(string message);
@ -25,14 +24,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>
@ -61,7 +59,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);
}
}
}
@ -72,21 +70,25 @@ namespace winsw
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 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)
{
@ -117,25 +119,27 @@ 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; }
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();
}
}
@ -160,7 +164,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();
@ -170,23 +174,24 @@ namespace winsw
public class TimeBasedRollingLogAppender : AbstractFileLogAppender
{
public string Pattern { get; private set; }
public int Period { get; private set; }
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>
@ -194,17 +199,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);
@ -217,14 +222,10 @@ 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 static int BytesPerKB = 1024;
public static int BytesPerMB = 1024 * BytesPerKB;
public static int DefaultSizeThreshold = 10 * BytesPerMB; // roll every 10MB.
public static int DefaultFilesToKeep = 8;
public int SizeTheshold { get; private set; }
@ -233,21 +234,23 @@ namespace winsw
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.SizeTheshold = 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>
@ -255,41 +258,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.SizeTheshold)
{
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);
@ -311,25 +318,34 @@ 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 static int BytesPerKB = 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 RollingSizeTimeLogAppender(
@ -346,21 +362,21 @@ namespace winsw
string zipdateformat)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{
SizeTheshold = sizeThreshold;
FilePattern = filePattern;
AutoRollAtTime = autoRollAtTime;
ZipOlderThanNumDays = zipolderthannumdays;
ZipDateFormat = zipdateformat;
this.SizeTheshold = 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)
@ -368,18 +384,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) =>
{
@ -391,25 +407,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();
}
};
@ -422,26 +438,26 @@ namespace winsw
lock (fileLock)
{
int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char);
if (fileLength + lengthToWrite > SizeTheshold)
if (fileLength + lengthToWrite > this.SizeTheshold)
{
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}");
}
}
@ -456,28 +472,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}");
}
}
@ -496,7 +516,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
{
@ -521,7 +541,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}");
zipFile?.AbortUpdate();
}
finally
@ -543,7 +563,9 @@ namespace winsw
autoRollAtTime.Seconds,
0);
if (nowTime > scheduledTime)
{
scheduledTime = scheduledTime.AddDays(1);
}
double tickTime = (scheduledTime - DateTime.Now).TotalMilliseconds;
return tickTime;
@ -552,7 +574,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;
@ -566,11 +588,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
{
@ -584,7 +607,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,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,7 +1,7 @@
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace winsw.Native
namespace WinSW.Native
{
internal static class Kernel32
{

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
{

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

@ -1,8 +1,8 @@
using System;
using System.Security.AccessControl;
using static winsw.Native.ServiceApis;
using static WinSW.Native.ServiceApis;
namespace winsw.Native
namespace WinSW.Native
{
public enum SC_ACTION_TYPE
{
@ -106,7 +106,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

@ -2,7 +2,7 @@
using System.Runtime.InteropServices;
using System.Security.AccessControl;
namespace winsw.Native
namespace WinSW.Native
{
internal static class ServiceApis
{

View File

@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Diagnostics;
namespace winsw.Native
namespace WinSW.Native
{
internal static class Throw
{

View File

@ -1,4 +1,5 @@
#if !NETCOREAPP
#pragma warning disable SA1502 // Element should not be on a single line
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>

View File

@ -1,33 +1,29 @@
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.PeriodicityType = this.DeterminePeriodicityType();
this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period);
}
public enum PeriodicityType
public enum Periodicity
{
ERRONEOUS,
TOP_OF_MILLISECOND,
@ -37,23 +33,23 @@ namespace winsw
TOP_OF_DAY
}
private static readonly PeriodicityType[] VALID_ORDERED_LIST =
private static readonly Periodicity[] ValidOrderedList =
{
PeriodicityType.TOP_OF_MILLISECOND, PeriodicityType.TOP_OF_SECOND, PeriodicityType.TOP_OF_MINUTE, PeriodicityType.TOP_OF_HOUR, PeriodicityType.TOP_OF_DAY
Periodicity.TOP_OF_MILLISECOND, Periodicity.TOP_OF_SECOND, Periodicity.TOP_OF_MINUTE, Periodicity.TOP_OF_HOUR, Periodicity.TOP_OF_DAY
};
private PeriodicityType determinePeriodicityType()
private Periodicity 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 (Periodicity i in ValidOrderedList)
{
string r0 = epoch.ToString(_format);
periodicRollingCalendar.periodicityType = i;
string r0 = epoch.ToString(this.format);
periodicRollingCalendar.PeriodicityType = 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)
{
@ -61,45 +57,45 @@ namespace winsw
}
}
return PeriodicityType.ERRONEOUS;
return Periodicity.ERRONEOUS;
}
private DateTime nextTriggeringTime(DateTime input, long increment) => periodicityType switch
private DateTime NextTriggeringTime(DateTime input, long increment) => this.PeriodicityType switch
{
PeriodicityType.TOP_OF_MILLISECOND =>
Periodicity.TOP_OF_MILLISECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond)
.AddMilliseconds(increment),
PeriodicityType.TOP_OF_SECOND =>
Periodicity.TOP_OF_SECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second)
.AddSeconds(increment),
PeriodicityType.TOP_OF_MINUTE =>
Periodicity.TOP_OF_MINUTE =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, 0)
.AddMinutes(increment),
PeriodicityType.TOP_OF_HOUR =>
Periodicity.TOP_OF_HOUR =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0)
.AddHours(increment),
PeriodicityType.TOP_OF_DAY =>
Periodicity.TOP_OF_DAY =>
new DateTime(input.Year, input.Month, input.Day)
.AddDays(increment),
_ => throw new Exception("invalid periodicity type: " + periodicityType),
_ => throw new Exception("invalid periodicity type: " + this.PeriodicityType),
};
public PeriodicityType periodicityType { get; set; }
public Periodicity PeriodicityType { 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

@ -4,19 +4,18 @@ using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using winsw.Configuration;
using winsw.Native;
using winsw.Util;
using WinSW.Configuration;
using WinSW.Native;
using WinSW.Util;
using WMI;
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,7 +106,6 @@ namespace winsw
this.environmentVariables = this.LoadEnvironmentVariables();
}
// ReSharper disable once InconsistentNaming
public static ServiceDescriptor FromXML(string xml)
{
var dom = new XmlDocument();
@ -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,28 +657,28 @@ namespace winsw
return null;
}
protected string? AllowServiceLogon => GetServiceAccountPart("allowservicelogon");
protected string? AllowServiceLogon => this.GetServiceAccountPart("allowservicelogon");
protected internal string? ServiceAccountDomain => GetServiceAccountPart("domain");
protected internal string? ServiceAccountDomain => this.GetServiceAccountPart("domain");
protected internal string? ServiceAccountName => GetServiceAccountPart("user");
protected internal string? ServiceAccountName => this.GetServiceAccountPart("user");
public string? ServiceAccountPassword => GetServiceAccountPart("password");
public string? ServiceAccountPassword => this.GetServiceAccountPart("password");
public string? ServiceAccountUser => ServiceAccountName is null ? null : (ServiceAccountDomain ?? ".") + "\\" + ServiceAccountName;
public string? ServiceAccountUser => this.ServiceAccountName is null ? null : (this.ServiceAccountDomain ?? ".") + "\\" + this.ServiceAccountName;
public bool HasServiceAccount()
{
return !string.IsNullOrEmpty(ServiceAccountName);
return !string.IsNullOrEmpty(this.ServiceAccountName);
}
public bool AllowServiceAcountLogonRight
{
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;
}
@ -677,13 +691,13 @@ 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);
public bool StopParentProcessFirst
{
get
{
var value = SingleElement("stopparentprocessfirst", true);
var value = this.SingleElement("stopparentprocessfirst", true);
if (bool.TryParse(value, out bool result))
{
return result;
@ -700,19 +714,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

@ -5,7 +5,7 @@ using System.Management;
using System.Threading;
using log4net;
namespace winsw.Util
namespace WinSW.Util
{
/// <summary>
/// Provides helper classes for Process Management
@ -76,6 +76,7 @@ namespace winsw.Util
{
Logger.Warn("Process " + pid + " did not respond to Ctrl+C signal - Killing as fallback");
}
proc.Kill();
}
catch (Exception ex)
@ -126,14 +127,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,
@ -161,6 +162,7 @@ namespace winsw.Util
foreach (string key in envVars.Keys)
{
Environment.SetEnvironmentVariable(key, envVars[key]);
// DONTDO: ps.EnvironmentVariables[key] = envs[key];
// bugged (lower cases all variable names due to StringDictionary being used, see http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=326163)
}
@ -178,7 +180,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);
@ -102,7 +110,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);
}
#else

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

@ -48,7 +48,7 @@ namespace WMI
public WmiException(string message, ReturnValue code)
: base(message)
{
ErrorCode = code;
this.ErrorCode = code;
}
public WmiException(ReturnValue code)
@ -64,13 +64,15 @@ namespace WMI
{
public readonly string Name;
public WmiClassName(string name) => Name = name;
public WmiClassName(string name) => this.Name = name;
}
/// <summary>
/// Marker interface to denote a collection in WMI.
/// </summary>
public interface IWmiCollection { }
public interface IWmiCollection
{
}
/// <summary>
/// Marker interface to denote an individual managed object
@ -109,7 +111,9 @@ namespace WMI
{
uint code = (uint)result["returnValue"];
if (code != 0)
{
throw new WmiException((ReturnValue)code);
}
}
protected ManagementBaseObject GetMethodParameters(ManagementObject wmiObject, string methodName, ParameterInfo[] methodParameters, object[] arguments)
@ -174,20 +178,23 @@ namespace WMI
{
ParameterInfo[] methodParameters = method.GetParameters();
if (method.Name == nameof(Win32Services.Select))
if (method.Name == nameof(IWin32Services.Select))
{
// select method to find instances
StringBuilder query = new StringBuilder("SELECT * FROM ").Append(this.className).Append(" WHERE ");
for (int i = 0; i < arguments.Length; i++)
{
if (i != 0)
{
query.Append(" AND ");
}
query.Append(' ').Append(Capitalize(methodParameters[i].Name!)).Append(" = '").Append(arguments[i]).Append('\'');
}
using ManagementObjectSearcher searcher = new ManagementObjectSearcher(this.wmiClass.Scope, new ObjectQuery(query.ToString()));
using ManagementObjectCollection results = searcher.Get();
// TODO: support collections
foreach (ManagementObject wmiObject in results)
{
@ -209,7 +216,8 @@ namespace WMI
/// <summary>
/// Obtains an object that corresponds to a table in WMI, which is a collection of a managed object.
/// </summary>
public T GetCollection<T>() where T : IWmiCollection
public T GetCollection<T>()
where T : IWmiCollection
{
WmiClassName className = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0];

View File

@ -1,5 +1,4 @@

namespace WMI
namespace WMI
{
public enum ServiceType
{
@ -26,18 +25,22 @@ namespace WMI
/// Device driver started by the operating system loader. This value is valid only for driver services.
/// </summary>
Boot,
/// <summary>
/// Device driver started by the operating system initialization process. This value is valid only for driver services.
/// </summary>
System,
/// <summary>
/// Service to be started automatically by the Service Control Manager during system startup.
/// </summary>
Automatic,
/// <summary>
/// Service to be started by the Service Control Manager when a process calls the StartService method.
/// </summary>
Manual,
/// <summary>
/// Service that can no longer be started.
/// </summary>
@ -45,22 +48,25 @@ namespace WMI
}
[WmiClassName("Win32_Service")]
public interface Win32Services : IWmiCollection
public interface IWin32Services : IWmiCollection
{
// ReturnValue Create(bool desktopInteract, string displayName, int errorControl, string loadOrderGroup, string loadOrderGroupDependencies, string name, string pathName, string serviceDependencies, string serviceType, string startMode, string startName, string startPassword);
void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string? startName, string? startPassword, string[] serviceDependencies);
void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string[] serviceDependencies);
Win32Service? Select(string name);
IWin32Service? Select(string name);
}
// https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service
public interface Win32Service : IWmiObject
public interface IWin32Service : IWmiObject
{
bool Started { get; }
void Delete();
void StartService();
void StopService();
}
}

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

@ -5,11 +5,11 @@ 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
{
@ -183,13 +183,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)!));
StopParentProcessFirst = bool.Parse(XmlHelper.SingleElement(node, "stopParentFirst", false)!);
ServiceId = descriptor.Id;
this.Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!;
this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!));
this.StopParentProcessFirst = bool.Parse(XmlHelper.SingleElement(node, "stopParentFirst", 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>
@ -200,16 +200,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;
}
@ -219,13 +219,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;
}
@ -243,9 +243,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.");
@ -254,10 +254,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;
}
@ -265,7 +265,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 ");
}
@ -285,14 +285,14 @@ namespace winsw.Plugins.RunawayProcessKiller
/// <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,10 +1,10 @@
using System.Collections.Generic;
using System.Xml;
using log4net;
using winsw.Extensions;
using winsw.Util;
using WinSW.Extensions;
using WinSW.Util;
namespace winsw.Plugins.SharedDirectoryMapper
namespace WinSW.Plugins.SharedDirectoryMapper
{
public class SharedDirectoryMapper : AbstractWinSWExtension
{
@ -22,7 +22,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
public SharedDirectoryMapper(bool enableMapping, string directoryUNC, string driveLabel)
{
SharedDirectoryMapperConfig config = new SharedDirectoryMapperConfig(enableMapping, driveLabel, directoryUNC);
_entries.Add(config);
this._entries.Add(config);
}
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
@ -35,7 +35,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
if (mapNodes[i] is XmlElement mapElement)
{
var config = SharedDirectoryMapperConfig.FromXml(mapElement);
_entries.Add(config);
this._entries.Add(config);
}
}
}
@ -43,40 +43,40 @@ namespace winsw.Plugins.SharedDirectoryMapper
public override void OnWrapperStarted()
{
foreach (SharedDirectoryMapperConfig config in _entries)
foreach (SharedDirectoryMapperConfig config in this._entries)
{
if (config.EnableMapping)
{
Logger.Info(DisplayName + ": Mapping shared directory " + config.UNCPath + " to " + config.Label);
Logger.Info(this.DisplayName + ": Mapping shared directory " + config.UNCPath + " to " + config.Label);
try
{
_mapper.MapDirectory(config.Label, config.UNCPath);
this._mapper.MapDirectory(config.Label, config.UNCPath);
}
catch (MapperException ex)
{
HandleMappingError(config, ex);
this.HandleMappingError(config, ex);
}
}
else
{
Logger.Warn(DisplayName + ": Mapping of " + config.Label + " is disabled");
Logger.Warn(this.DisplayName + ": Mapping of " + config.Label + " is disabled");
}
}
}
public override void BeforeWrapperStopped()
{
foreach (SharedDirectoryMapperConfig config in _entries)
foreach (SharedDirectoryMapperConfig config in this._entries)
{
if (config.EnableMapping)
{
try
{
_mapper.UnmapDirectory(config.Label);
this._mapper.UnmapDirectory(config.Label);
}
catch (MapperException ex)
{
HandleMappingError(config, ex);
this.HandleMappingError(config, ex);
}
}
}
@ -86,7 +86,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
{
Logger.Error("Mapping of " + config.Label + " failed. STDOUT: " + ex.Process.StandardOutput.ReadToEnd()
+ " \r\nSTDERR: " + ex.Process.StandardError.ReadToEnd(), ex);
throw new ExtensionException(Descriptor.Id, DisplayName + ": Mapping of " + config.Label + "failed", ex);
throw new ExtensionException(this.Descriptor.Id, this.DisplayName + ": Mapping of " + config.Label + "failed", ex);
}
}
}

View File

@ -1,7 +1,7 @@
using System.Xml;
using winsw.Util;
using WinSW.Util;
namespace winsw.Plugins.SharedDirectoryMapper
namespace WinSW.Plugins.SharedDirectoryMapper
{
/// <summary>
/// Stores configuration entries for SharedDirectoryMapper extension.
@ -14,9 +14,9 @@ namespace winsw.Plugins.SharedDirectoryMapper
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,6 +1,6 @@
using System.Diagnostics;
namespace winsw.Plugins.SharedDirectoryMapper
namespace WinSW.Plugins.SharedDirectoryMapper
{
class SharedDirectoryMappingHelper
{
@ -42,7 +42,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
/// <exception cref="MapperException">Operation failure</exception>
public void MapDirectory(string label, string uncPath)
{
InvokeCommand("net.exe", " use " + label + " " + uncPath);
this.InvokeCommand("net.exe", " use " + label + " " + uncPath);
}
/// <summary>
@ -52,7 +52,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
/// <exception cref="MapperException">Operation failure</exception>
public void UnmapDirectory(string label)
{
InvokeCommand("net.exe", " use /DELETE /YES " + label);
this.InvokeCommand("net.exe", " use /DELETE /YES " + label);
}
}
@ -64,8 +64,8 @@ namespace winsw.Plugins.SharedDirectoryMapper
public MapperException(Process process, string command, string args)
: base("Command " + command + " " + args + " failed with code " + process.ExitCode)
{
Call = command + " " + args;
Process = process;
this.Call = command + " " + args;
this.Process = process;
}
}
}

View File

@ -2,7 +2,7 @@
using System.Reflection;
using System.Xml;
using NUnit.Framework;
using winsw;
using WinSW;
using winswTests.Util;
namespace winswTests.Configuration

View File

@ -1,6 +1,6 @@
using System.IO;
using NUnit.Framework;
using winsw;
using WinSW;
using winswTests.Util;
namespace winswTests
@ -19,11 +19,11 @@ namespace winswTests
var sd = ConfigXmlBuilder.create()
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd);
var loaded = this.GetSingleEntry(sd);
// Check default values
Assert.That(loaded.FailOnError, Is.False);
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.none));
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.None));
Assert.That(loaded.Username, Is.Null);
Assert.That(loaded.Password, Is.Null);
Assert.That(loaded.UnsecureAuth, Is.False);
@ -33,15 +33,15 @@ namespace winswTests
public void Roundtrip_BasicAuth()
{
// Roundtrip data
Download d = new Download(From, To, true, Download.AuthType.basic, "aUser", "aPassword", true);
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.That(loaded.FailOnError, Is.True);
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.basic));
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.Basic));
Assert.That(loaded.Username, Is.EqualTo("aUser"));
Assert.That(loaded.Password, Is.EqualTo("aPassword"));
Assert.That(loaded.UnsecureAuth, Is.True);
@ -51,15 +51,15 @@ namespace winswTests
public void Roundtrip_SSPI()
{
// Roundtrip data
Download d = new Download(From, To, false, Download.AuthType.sspi);
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.That(loaded.FailOnError, Is.False);
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.sspi));
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.Sspi));
Assert.That(loaded.Username, Is.Null);
Assert.That(loaded.Password, Is.Null);
Assert.That(loaded.UnsecureAuth, Is.False);
@ -73,22 +73,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");
}
[Test]
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");
}
[Test]
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>
@ -104,7 +104,7 @@ namespace winswTests
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd);
var loaded = this.GetSingleEntry(sd);
Assert.That(loaded.From, Is.EqualTo(From));
Assert.That(loaded.To, Is.EqualTo(To));
Assert.That(loaded.FailOnError, Is.EqualTo(failOnError), "Unexpected FailOnError value");
@ -120,7 +120,7 @@ namespace winswTests
.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.That(loaded.FailOnError, Is.False);
}
@ -133,8 +133,8 @@ namespace winswTests
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.That(loaded.Auth, Is.EqualTo(Download.AuthType.sspi));
var loaded = this.GetSingleEntry(sd);
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.Sspi));
}
[Test]
@ -145,7 +145,7 @@ namespace winswTests
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"digest\"/>")
.ToServiceDescriptor(true);
Assert.That(() => GetSingleEntry(sd), Throws.TypeOf<InvalidDataException>().With.Message.StartsWith("Cannot parse <auth> Enum value from string 'digest'"));
Assert.That(() => this.GetSingleEntry(sd), Throws.TypeOf<InvalidDataException>().With.Message.StartsWith("Cannot parse <auth> Enum value from string 'digest'"));
}
[TestCase("http://", "127.0.0.1:80", "egarcia", "Passw0rd")]
@ -190,7 +190,7 @@ namespace winswTests
.WithDownload(download)
.ToServiceDescriptor(true);
Assert.That(() => GetSingleEntry(sd), Throws.TypeOf<InvalidDataException>().With.Message.StartsWith(expectedMessagePart));
Assert.That(() => this.GetSingleEntry(sd), Throws.TypeOf<InvalidDataException>().With.Message.StartsWith(expectedMessagePart));
}
}
}

View File

@ -6,7 +6,7 @@ using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using NUnit.Framework;
using winsw;
using WinSW;
using winswTests.Util;
namespace winswTests
@ -111,7 +111,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.That(File.ReadAllBytes(dest), Is.EqualTo(this.contents));
},
context =>
@ -136,7 +136,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.That(File.ReadAllBytes(dest), Is.EqualTo(this.contents));
},
context =>

View File

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using NUnit.Framework;
using winsw;
using winsw.Extensions;
using winsw.Plugins.RunawayProcessKiller;
using winsw.Util;
using WinSW;
using WinSW.Extensions;
using WinSW.Plugins.RunawayProcessKiller;
using WinSW.Util;
using winswTests.Util;
namespace winswTests.Extensions
@ -30,20 +30,20 @@ $@"<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>
<stopParentFirst>true</stopParentFirst>
</extension>
</extensions>
</service>";
_testServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
this._testServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
}
[Test]
public void LoadExtensions()
{
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this._testServiceDescriptor);
manager.LoadExtensions();
Assert.AreEqual(1, manager.Extensions.Count, "One extension should be loaded");
@ -58,7 +58,7 @@ $@"<service>
[Test]
public void StartStopExtension()
{
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this._testServiceDescriptor);
manager.LoadExtensions();
manager.FireOnWrapperStarted();
manager.FireBeforeWrapperStopped();
@ -80,7 +80,7 @@ $@"<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

View File

@ -1,7 +1,7 @@
using NUnit.Framework;
using winsw;
using winsw.Extensions;
using winsw.Plugins.SharedDirectoryMapper;
using WinSW;
using WinSW.Extensions;
using WinSW.Plugins.SharedDirectoryMapper;
namespace winswTests.Extensions
{
@ -24,13 +24,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""/>
@ -38,13 +38,13 @@ $@"<service>
</extension>
</extensions>
</service>";
_testServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
this._testServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
}
[Test]
public void LoadExtensions()
{
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this._testServiceDescriptor);
manager.LoadExtensions();
Assert.AreEqual(2, manager.Extensions.Count, "Two extensions should be loaded");
}
@ -52,7 +52,7 @@ $@"<service>
[Test]
public void StartStopExtension()
{
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this._testServiceDescriptor);
manager.LoadExtensions();
manager.FireOnWrapperStarted();
manager.FireBeforeWrapperStopped();

View File

@ -1,7 +1,7 @@
using System;
using System.ServiceProcess;
using NUnit.Framework;
using winsw;
using WinSW;
using winswTests.Util;
namespace winswTests

View File

@ -1,7 +1,7 @@
using System;
using System.Diagnostics;
using NUnit.Framework;
using winsw;
using WinSW;
using winswTests.Util;
using WMI;
@ -38,13 +38,13 @@ $@"<service>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
}
[Test]
public void DefaultStartMode()
{
Assert.That(_extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
}
[Test]
@ -69,8 +69,8 @@ $@"<service>
<logpath>C:\logs</logpath>
</service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(() => _extendedServiceDescriptor.StartMode, Throws.ArgumentException);
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(() => this._extendedServiceDescriptor.StartMode, Throws.ArgumentException);
}
[Test]
@ -95,35 +95,35 @@ $@"<service>
<logpath>C:\logs</logpath>
</service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(_extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
}
[Test]
public void VerifyWorkingDirectory()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.That(_extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory));
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory));
}
[Test]
public void VerifyServiceLogonRight()
{
Assert.That(_extendedServiceDescriptor.AllowServiceAcountLogonRight, Is.True);
Assert.That(this._extendedServiceDescriptor.AllowServiceAcountLogonRight, Is.True);
}
[Test]
public void VerifyUsername()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.That(_extendedServiceDescriptor.ServiceAccountUser, Is.EqualTo(Domain + "\\" + Username));
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.ServiceAccountUser, Is.EqualTo(Domain + "\\" + Username));
}
[Test]
public void VerifyPassword()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.That(_extendedServiceDescriptor.ServiceAccountPassword, Is.EqualTo(Password));
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.ServiceAccountPassword, Is.EqualTo(Password));
}
[Test]
@ -142,7 +142,7 @@ $@"<service>
[Test]
public void StopParentProcessFirstIsFalseByDefault()
{
Assert.That(_extendedServiceDescriptor.StopParentProcessFirst, Is.False);
Assert.That(this._extendedServiceDescriptor.StopParentProcessFirst, Is.False);
}
[Test]

View File

@ -1,7 +1,7 @@
using System;
using System.IO;
using NUnit.Framework;
using winsw;
using WinSW;
namespace winswTests.Util
{
@ -115,13 +115,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,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using winsw;
using winsw.Plugins.RunawayProcessKiller;
using WinSW;
using WinSW.Plugins.RunawayProcessKiller;
using winswTests.Extensions;
namespace winswTests.Util
@ -25,8 +25,8 @@ namespace winswTests.Util
// 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,
@ -48,33 +48,33 @@ namespace winswTests.Util
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 +95,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)
@ -119,7 +119,7 @@ namespace winswTests.Util
str.AppendFormat(" <stopParentFirst>{0}</stopParentFirst>\n", ext.StopParentProcessFirst);
str.AppendFormat(" <checkWinSWEnvironmentVariable>{0}</checkWinSWEnvironmentVariable>\n", ext.CheckWinSWEnvironmentVariable);
str.Append(" </extension>\n");
ExtensionXmls.Add(str.ToString());
this.ExtensionXmls.Add(str.ToString());
return this;
}
@ -130,10 +130,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)
@ -156,12 +156,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

@ -3,7 +3,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using NUnit.Framework;
using winsw.Util;
using WinSW.Util;
namespace winswTests.Util
{

View File

@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Reflection;
using NUnit.Framework;
using winsw;
using winsw.Configuration;
using WinSW;
using WinSW.Configuration;
namespace winswTests.Util
{

View File

@ -1,5 +1,5 @@
using NUnit.Framework;
using winsw;
using WinSW;
namespace winswTests.Util
{

7
src/stylecop.json Normal file
View File

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

View File

@ -20,6 +20,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunawayProcessKiller", "Plu
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AA414F46-B863-473A-A0E0-C2971B3396AE}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
..\examples\sample-allOptions.xml = ..\examples\sample-allOptions.xml
..\examples\sample-minimal.xml = ..\examples\sample-minimal.xml
EndProjectSection

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>