Merge branch 'master' into filelen

pull/582/head
Oleg Nenashev 2020-08-05 11:03:39 +02:00 committed by GitHub
commit 62d10fe2fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 4386 additions and 2841 deletions

4
.github/codecov.yml vendored Normal file
View File

@ -0,0 +1,4 @@
coverage:
status:
project: off
patch: off

304
doc/YamlConfigFile.md Normal file
View File

@ -0,0 +1,304 @@
# YAML configuration file
This page describes the configuration file, which controls the behavior of the Windows service.
You can find configuration file samples in the [examples](../examples) directory of the source code repository.
Actual samples are also being published as part of releases on GitHub and NuGet.
## File structure
YAML Configuration file shuold be in following format
Example:
```yaml
id: jenkins
name: Jenkins
description: This service runs Jenkins continuous integration system.
env:
- name: JENKINS_HOME
value: '%BASE%'
executable: java
arguments: >
-Xrs
-Xmx256m
-jar "%BASE%\jenkins.war"
--httpPort=8080
log:
mode: roll
```
## Environment variable expansion
Configuration YAML files can include environment variable expansions of the form `%Name%`.
Such occurrences, if found, will be automatically replaced by the actual values of the variables.
[Read more about Environment variable expansion](xmlConfigFile.md#environment-variable-expansion)
## Configuration entries
### id
Specifies the ID that Windows uses internally to identify the service.
This has to be unique among all the services installed in a system, and it should consist entirely out of alpha-numeric characters.
### name
Short display name of the service, which can contain spaces and other characters.
This shouldn't be too long, like `id`, and this also needs to be unique among all the services in a given system.
### description
Long human-readable description of the service.
This gets displayed in Windows service manager when the service is selected.
### executable
This element specifies the executable to be launched.
It can be either absolute path, or you can just specify the executable name and let it be searched from `PATH` (although note that the services often run in a different user account and therefore it might have different `PATH` than your shell does.)
### startmode
This element specifies the start mode of the Windows service.
It can be one of the following values: Boot, System, Automatic, or Manual.
For more information, see the [ChangeStartMode method](https://docs.microsoft.com/windows/win32/cimwin32prov/changestartmode-method-in-class-win32-service).
The default value is `Automatic`.
### delayedAutoStart
This Boolean option enables the delayed start mode if the `Automatic` start mode is defined.
[Read more about delayedAutoStart](xmlConfigFile.md#delayedautostart)
```yaml
delayedAutoStart: false
```
### depend
Specify IDs of other services that this service depends on.
When service `X` depends on service `Y`, `X` can only run if `Y` is running.
YAML list can be used to specify multiple dependencies.
```yaml
depend:
- Eventlog
- W32Time
```
### log
Optionally set a different logging directory with `logpath` and startup `mode`: append (default), reset (clear log), ignore, roll (move to `\*.old`).
User can specify all log configurations as a single YAML dictionary
```yaml
log:
mode: roll-by-size
logpath: '%BASE/log%'
sizeThreshold: 10240
keepFiles: 8
```
See the [Logging and error reporting](loggingAndErrorReporting.md) page for more info.
### Arguments
`arguments` element specifies the arguments to be passed to the executable. User can specify all the commands as a single line.
```yaml
arguments: arg1 arg2 arg3
```
Also user can specify the arguemtns in more structured way with YAML multline strings.
```yaml
arguments: >
arg1
arg2
arg3
```
### stoparguments/stopexecutable
~~When the service is requested to stop, winsw simply calls [TerminateProcess function](https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess) to kill the service instantly.~~
However, if `stoparguments` elements is present, winsw will instead launch another process of `executable` (or `stopexecutable` if that's specified) with the specified arguments, and expects that to initiate the graceful shutdown of the service process.
Winsw will then wait for the two processes to exit on its own, before reporting back to Windows that the service has terminated.
When you use the `stoparguments`, you must use `startarguments` instead of `arguments`. See the complete example below:
```yaml
executable: catalina.sh
startarguments: >
jpda
run
stopexecutable: catalina.sh
stoparguments: stop
```
### stoptimeout
This optional element allows you to change this "15 seconds" value, so that you can control how long winsw gives the service to shut itself down.
[Read more about stoptimeout](xmlConfigFile.md#stoptimeout)
See `onfailure` below for how to specify time duration:
```yaml
stoptimeout: 15 sec
```
### Environment
User can use list of YAML dictionaries, if necessary to specify environment variables to be set for the child process. The syntax is:
```yaml
env:
-
name: MY_TOOL_HOME
value: 'C:\etc\tools\myTool'
-
name: LM_LICENSE_FILE
value: host1;host2
```
### interactive
If this optional element is specified, the service will be allowed to interact with the desktop, such as by showing a new window and dialog boxes.
If your program requires GUI, set this like the following:
```yaml
interactive: true
```
Note that since the introduction UAC (Windows Vista and onward), services are no longer really allowed to interact with the desktop.
In those OSes, all that this does is to allow the user to switch to a separate window station to interact with the service.
### beeponshutdown
This optional element is to emit [simple tones](https://docs.microsoft.com/windows/win32/api/utilapiset/nf-utilapiset-beep) when the service shuts down.
This feature should be used only for debugging, as some operating systems and hardware do not support this functionality.
### download
This optional element can be specified to have the service wrapper retrieve resources from URL and place it locally as a file.
This operation runs when the service is started, before the application specified by `executable` is launched.
[Read more about download](xmlConfigFile.md#download)
Examples:
```yaml
download:
-
from: "http://www.google.com/"
to: '%BASE%\index.html'
-
from: "http://www.nosuchhostexists.com/"
to: '%BASE%\dummy.html'
failOnError: true
-
from: "http://example.com/some.dat"
to: '%BASE%\some.dat'
auth: basic
unsecureAuth: true
username: aUser
password: aPa55w0rd
-
from: "https://example.com/some.dat"
to: '%BASE%\some.dat'
auth: basic
username: aUser
password: aPa55w0rd
-
from: "https://example.com/some.dat"
to: '%BASE%\some.dat'
auth: sspi
```
### onfailure
This optional element controls the behaviour when the process launched by winsw fails (i.e., exits with non-zero exit code).
```yaml
onFailure:
-
action: restart
delay: 10 sec
-
action: restart
delay: 20 sec
-
action: reboot
```
[Read more about onFailure](xmlConfigFile.md#onfailure)
### resetfailure
This optional element controls the timing in which Windows SCM resets the failure count.
For example, if you specify `resetfailure: 1 hour` and your service continues to run longer than one hour, then the failure count is reset to zero.
This affects the behaviour of the failure actions (see `onfailure` above).
In other words, this is the duration in which you consider the service has been running successfully.
Defaults to 1 day.
### Security descriptor
The security descriptor string for the service in SDDL form.
For more information, see [Security Descriptor Definition Language](https://docs.microsoft.com/windows/win32/secauthz/security-descriptor-definition-language).
```yaml
securtityDescriptor: 'D:(A;;DCSWRPDTRC;;;BA)(A;;DCSWRPDTRC;;;SY)S:NO\_ACCESS\_CONTROL'
```
### Service account
The service is installed as the [LocalSystem account](https://docs.microsoft.com/windows/win32/services/localsystem-account) by default. If your service does not need a high privilege level, consider using the [LocalService account](https://docs.microsoft.com/windows/win32/services/localservice-account), the [NetworkService account](https://docs.microsoft.com/windows/win32/services/networkservice-account) or a user account.
To use a user account, specify a `serviceaccount` element like this:
```yaml
serviceaccount:
domain: YOURDOMAIN
user: useraccount
password: Pa55w0rd
allowservicelogon: true
```
[Read more about Service account](xmlConfigFile.md#service-account)
### Working directory
Some services need to run with a working directory specified.
To do this, specify a `workingdirectory` element like this:
```yaml
workingdirectory: 'C:\application'
```
### Priority
Optionally specify the scheduling priority of the service process (equivalent of Unix nice)
Possible values are `idle`, `belownormal`, `normal`, `abovenormal`, `high`, `realtime` (case insensitive.)
```yaml
priority: idle
```
Specifying a priority higher than normal has unintended consequences.
For more information, see [ProcessPriorityClass Enumeration](https://docs.microsoft.com/dotnet/api/system.diagnostics.processpriorityclass) in .NET docs.
This feature is intended primarily to launch a process in a lower priority so as not to interfere with the computer's interactive usage.
### Stop parent process first
Optionally specify the order of service shutdown.
If `true`, the parent process is shutdown first.
This is useful when the main process is a console, which can respond to Ctrl+C command and will gracefully shutdown child processes.
```yaml
stopparentprocessfirst: true
```

View File

@ -0,0 +1,75 @@
id: myapp
name: MyApp Service (powered by WinSW)
description: This service is a service created from a sample configuration
executable: java
#serviceaccount:
# domain: YOURDOMAIN
# user: useraccount
# password: Pa55w0rd
# allowservicelogon: yes
#onFailure:
# -
# action: restart
# delay: 10 sec
# -
# action: restart
# delay: 20 sec
# -
# action: reboot
#resetFailureAfter: 01:00:00
#securityDescriptor: security descriptor string
#arguments: >
# -classpath
# c:\cygwin\home\kohsuke\ws\hello-world\out\production\hello-world
# test.Main
#startArguments: start arguments
#workingdirectory: C:\myApp\work
priority: Normal
stopTimeout: 15 sec
stopParentProcessFirst: true
#stopExecutable: '%BASE%\stop.exe'
#stopArguments: -stop true
startMode: Automatic
#delayedAutoStart: true
#serviceDependencies:
# - Eventlog
# - W32Time
waitHint: 15 sec
sleepTime: 1 sec
#interactive: true
log:
# logpath: '%BASE%\logs'
mode: append
#env:
# -
# name: MY_TOOL_HOME
# value: 'C:\etc\tools\myTool'
# -
# name: LM_LICENSE_FILE
# value: host1;host2
#download:
# -
# from: "http://www.google.com/"
# to: '%BASE%\index.html'
# -
# from: "http://www.nosuchhostexists.com/"
# to: '%BASE%\dummy.html'
# failOnError: true
# -
# from: "http://example.com/some.dat"
# to: '%BASE%\some.dat'
# auth: basic
# unsecureAuth: true
# username: aUser
# password: aPa55w0rd
# -
# from: "https://example.com/some.dat"
# to: '%BASE%\some.dat'
# auth: basic
# username: aUser
# password: aPa55w0rd
# -
# from: "https://example.com/some.dat"
# to: '%BASE%\some.dat'
# auth: sspi
#beepOnShutdown: true

View File

@ -18,9 +18,9 @@ using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using WinSW.Configuration;
using WinSW.Logging;
using WinSW.Native;
using WinSW.Util;
using WMI;
using ServiceType = WMI.ServiceType;
@ -59,7 +59,7 @@ namespace WinSW
}
}
public static void Run(string[] argsArray, ServiceDescriptor? descriptor = null)
public static void Run(string[] argsArray, IWinSWConfiguration? descriptor = null)
{
bool inConsoleMode = argsArray.Length > 0;
@ -181,7 +181,9 @@ namespace WinSW
default:
Console.WriteLine("Unknown command: " + args[0]);
PrintAvailableCommands();
#pragma warning disable S112 // General exceptions should never be thrown
throw new Exception("Unknown command: " + args[0]);
#pragma warning restore S112 // General exceptions should never be thrown
}
void Install()
@ -199,7 +201,9 @@ namespace WinSW
{
Console.WriteLine("Service with id '" + descriptor.Id + "' already exists");
Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file");
#pragma warning disable S112 // General exceptions should never be thrown
throw new Exception("Installation failure: Service with id '" + descriptor.Id + "' already exists");
#pragma warning restore S112 // General exceptions should never be thrown
}
string? username = null;
@ -222,17 +226,17 @@ namespace WinSW
}
else
{
if (descriptor.HasServiceAccount())
if (descriptor.ServiceAccount.HasServiceAccount())
{
username = descriptor.ServiceAccountUser;
password = descriptor.ServiceAccountPassword;
allowServiceLogonRight = descriptor.AllowServiceAcountLogonRight;
username = descriptor.ServiceAccount.ServiceAccountUser;
password = descriptor.ServiceAccount.ServiceAccountPassword;
allowServiceLogonRight = descriptor.ServiceAccount.AllowServiceAcountLogonRight;
}
}
if (allowServiceLogonRight)
{
Security.AddServiceLogonRight(descriptor.ServiceAccountDomain!, descriptor.ServiceAccountName!);
Security.AddServiceLogonRight(descriptor.ServiceAccount.ServiceAccountDomain!, descriptor.ServiceAccount.ServiceAccountName!);
}
svcs.Create(
@ -319,7 +323,7 @@ namespace WinSW
Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. WMI Error code is '" + e.ErrorCode + "'");
}
throw e;
throw;
}
}
@ -455,14 +459,18 @@ namespace WinSW
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)
{
#pragma warning disable S112 // General exceptions should never be thrown
throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
#pragma warning restore S112 // General exceptions should never be thrown
}
}
void Status()
{
Log.Debug("User requested the status of the process with id '" + descriptor.Id + "'");
#pragma warning disable S3358 // Ternary operators should not be nested
Console.WriteLine(svc is null ? "NonExistent" : svc.Started ? "Started" : "Stopped");
#pragma warning restore S3358 // Ternary operators should not be nested
}
void Test()
@ -532,7 +540,7 @@ namespace WinSW
[DoesNotReturn]
private static void ThrowNoSuchService() => throw new WmiException(ReturnValue.NoSuchService);
private static void InitLoggers(ServiceDescriptor descriptor, bool enableConsoleLogging)
private static void InitLoggers(IWinSWConfiguration descriptor, bool enableConsoleLogging)
{
// TODO: Make logging levels configurable
Level fileLogLevel = Level.Debug;

View File

@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
#endif
using log4net;
using WinSW.Configuration;
using WinSW.Extensions;
using WinSW.Logging;
using WinSW.Native;
@ -20,9 +21,11 @@ namespace WinSW
public class WrapperService : ServiceBase, IEventLogger
{
private ServiceApis.SERVICE_STATUS wrapperServiceStatus;
private readonly Process process = new Process();
private readonly ServiceDescriptor descriptor;
private readonly IWinSWConfiguration descriptor;
private Dictionary<string, string>? envs;
internal WinSWExtensionManager ExtensionManager { get; private set; }
@ -55,7 +58,7 @@ namespace WinSW
/// </summary>
public bool IsShuttingDown => this.systemShuttingdown;
public WrapperService(ServiceDescriptor descriptor)
public WrapperService(IWinSWConfiguration descriptor)
{
this.descriptor = descriptor;
this.ServiceName = this.descriptor.Id;
@ -131,14 +134,15 @@ namespace WinSW
/// <returns>Log Handler, which should be used for the spawned process</returns>
private LogHandler CreateExecutableLogHandler()
{
string logDirectory = this.descriptor.LogDirectory;
string? logDirectory = this.descriptor.LogDirectory;
if (!Directory.Exists(logDirectory))
{
Directory.CreateDirectory(logDirectory);
}
LogHandler logAppender = this.descriptor.LogHandler;
LogHandler logAppender = this.descriptor.Log.CreateLogHandler();
logAppender.EventLogger = this;
return logAppender;
}

View File

@ -92,6 +92,7 @@
<InputAssemblies>$(InputAssemblies) "$(OutDir)SharedDirectoryMapper.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)RunawayProcessKiller.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)log4net.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)YamlDotNet.dll"</InputAssemblies>
<OutputAssembly>"$(ArtifactsDir)WinSW.$(IdentifierSuffix).exe"</OutputAssembly>
</PropertyGroup>

View File

@ -12,6 +12,8 @@ namespace WinSW.Configuration
/// </summary>
public sealed class DefaultWinSWSettings : IWinSWConfiguration
{
public static LogDefaults DefaultLogSettings { get; } = new LogDefaults();
public string Id => throw new InvalidOperationException(nameof(this.Id) + " must be specified.");
public string Caption => throw new InvalidOperationException(nameof(this.Caption) + " must be specified.");
@ -25,12 +27,6 @@ namespace WinSW.Configuration
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);
@ -66,17 +62,67 @@ namespace WinSW.Configuration
public bool Interactive => false;
// Logging
public string LogDirectory => Path.GetDirectoryName(this.ExecutablePath)!;
public Log Log { get => new LogDefaults(); }
public string LogMode => "append";
public string LogDirectory => DefaultLogSettings.Directory;
public bool OutFileDisabled => false;
public string LogMode => DefaultLogSettings.Mode;
public bool ErrFileDisabled => false;
public bool OutFileDisabled => this.Log.OutFileDisabled;
public string OutFilePattern => ".out.log";
public bool ErrFileDisabled => this.Log.ErrFileDisabled;
public string ErrFilePattern => ".err.log";
public string OutFilePattern => this.Log.OutFilePattern;
public string ErrFilePattern => this.Log.ErrFilePattern;
public ServiceAccount ServiceAccount => new ServiceAccount()
{
ServiceAccountName = null,
ServiceAccountDomain = null,
ServiceAccountPassword = null,
AllowServiceAcountLogonRight = false
};
public class LogDefaults : Log
{
private readonly DefaultWinSWSettings defaults;
public LogDefaults()
{
this.defaults = new DefaultWinSWSettings();
}
public override string Mode => "append";
public override string Name => this.defaults.BaseName;
public override string Directory => Path.GetDirectoryName(this.defaults.ExecutablePath)!;
public override int? SizeThreshold => 1024 * 10 * RollingSizeTimeLogAppender.BytesPerKB;
public override int? KeepFiles => SizeBasedRollingLogAppender.DefaultFilesToKeep;
public override string Pattern =>
throw new InvalidDataException("Time Based rolling policy is specified but no pattern can be found in configuration XML.");
public override int? Period => 1;
public override bool OutFileDisabled { get => false; }
public override bool ErrFileDisabled { get => false; }
public override string OutFilePattern { get => ".out.log"; }
public override string ErrFilePattern { get => ".err.log"; }
public override string? AutoRollAtTime => null;
public override int? ZipOlderThanNumDays =>
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but zipOlderThanNumDays does not match the int format found in configuration XML.");
public override string? ZipDateFormat => null;
}
// Environment
public List<Download> Downloads => new List<Download>(0);
@ -88,5 +134,32 @@ namespace WinSW.Configuration
// Extensions
public XmlNode? ExtensionsConfiguration => null;
public string BaseName
{
get
{
string baseName = Path.GetFileNameWithoutExtension(this.ExecutablePath);
if (baseName.EndsWith(".vshost"))
{
baseName = baseName.Substring(0, baseName.Length - 7);
}
return baseName;
}
}
public string BasePath
{
get
{
var d = new DirectoryInfo(Path.GetDirectoryName(this.ExecutablePath));
return Path.Combine(d.FullName, this.BaseName);
}
}
public List<string> ExtensionIds => new List<string>(0);
public string? SecurityDescriptor => null;
}
}

View File

@ -22,12 +22,6 @@ namespace WinSW.Configuration
bool HideWindow { get; }
// Installation
bool AllowServiceAcountLogonRight { get; }
string? ServiceAccountPassword { get; }
string? ServiceAccountUser { get; }
Native.SC_ACTION[] FailureActions { get; }
TimeSpan ResetFailureAfter { get; }
@ -60,12 +54,17 @@ namespace WinSW.Configuration
bool Interactive { get; }
// Logging
/// <summary>
/// Destination for logging.
/// If undefined, a default one should be used.
/// </summary>
string LogDirectory { get; }
// TODO: replace by enum
string LogMode { get; }
Log Log { get; }
// Environment
List<Download> Downloads { get; }
@ -76,5 +75,18 @@ namespace WinSW.Configuration
// Extensions
XmlNode? ExtensionsConfiguration { get; }
List<string> ExtensionIds { get; }
// Service Account
ServiceAccount ServiceAccount { get; }
string BaseName { get; }
string BasePath { get; }
bool DelayedAutoStart { get; }
string? SecurityDescriptor { get; }
}
}

View File

@ -0,0 +1,107 @@
using System;
using System.IO;
namespace WinSW.Configuration
{
public abstract class Log
{
public abstract string? Mode { get; }
public abstract string Name { get; }
public abstract string Directory { get; }
public abstract int? SizeThreshold { get; }
public abstract int? KeepFiles { get; }
public abstract string Pattern { get; }
public abstract int? Period { get; }
// Filters
public abstract bool OutFileDisabled { get; }
public abstract bool ErrFileDisabled { get; }
public abstract string OutFilePattern { get; }
public abstract string ErrFilePattern { get; }
// Zip options
public abstract string? AutoRollAtTime { get; }
public abstract int? ZipOlderThanNumDays { get; }
public abstract string? ZipDateFormat { get; }
public LogHandler CreateLogHandler()
{
switch (this.Mode)
{
case "rotate":
return new SizeBasedRollingLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "none":
return new IgnoreLogAppender();
case "reset":
return new ResetLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll":
return new RollingLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll-by-time":
return new TimeBasedRollingLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, this.Pattern, this.Period.GetValueOrDefault(1));
case "roll-by-size":
return new SizeBasedRollingLogAppender(
this.Directory,
this.Name,
this.OutFileDisabled,
this.ErrFileDisabled,
this.OutFilePattern,
this.ErrFilePattern,
this.SizeThreshold.GetValueOrDefault(10 * 1024) * SizeBasedRollingLogAppender.BytesPerKB,
this.KeepFiles.GetValueOrDefault(SizeBasedRollingLogAppender.DefaultFilesToKeep));
case "append":
return new DefaultLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll-by-size-time":
if (this.Pattern is null)
{
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but no pattern can be found in configuration");
}
TimeSpan? autoRollAtTime = null;
if (this.AutoRollAtTime != null)
{
// validate it
if (!TimeSpan.TryParse(this.AutoRollAtTime, 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;
}
return new RollingSizeTimeLogAppender(
this.Directory,
this.Name,
this.OutFileDisabled,
this.ErrFileDisabled,
this.OutFilePattern,
this.ErrFilePattern,
this.SizeThreshold.GetValueOrDefault(10 * 1024) * SizeBasedRollingLogAppender.BytesPerKB,
this.Pattern,
autoRollAtTime,
this.ZipOlderThanNumDays,
this.ZipDateFormat != null ? this.ZipDateFormat : "yyyyMM");
default:
throw new InvalidDataException("Undefined logging mode: " + this.Mode);
}
}
}
}

View File

@ -0,0 +1,29 @@
using YamlDotNet.Serialization;
namespace WinSW.Configuration
{
public class ServiceAccount
{
[YamlMember(Alias = "user")]
public string? ServiceAccountName { get; set; }
[YamlMember(Alias = "domain")]
public string? ServiceAccountDomain { get; set; }
[YamlMember(Alias = "password")]
public string? ServiceAccountPassword { get; set; }
[YamlMember(Alias = "allowservicelogon")]
public bool AllowServiceAcountLogonRight { get; set; }
public string? ServiceAccountUser
{
get => this.ServiceAccountName is null ? null : (this.ServiceAccountDomain ?? ".") + "\\" + this.ServiceAccountName;
}
public bool HasServiceAccount()
{
return !string.IsNullOrEmpty(this.ServiceAccountName);
}
}
}

View File

@ -0,0 +1,666 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
using WinSW.Native;
using WinSW.Util;
using WMI;
using YamlDotNet.Serialization;
using static WinSW.Download;
namespace WinSW.Configuration
{
public class YamlConfiguration : IWinSWConfiguration
{
public DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
[YamlMember(Alias = "id")]
public string? IdYaml { get; set; }
[YamlMember(Alias = "name")]
public string? NameYaml { get; set; }
[YamlMember(Alias = "description")]
public string? DescriptionYaml { get; set; }
[YamlMember(Alias = "executable")]
public string? ExecutableYaml { get; set; }
[YamlMember(Alias = "executablePath")]
public string? ExecutablePathYaml { get; set; }
[YamlMember(Alias = "hideWindow")]
public bool? HideWindowYaml { get; set; }
[YamlMember(Alias = "workingdirectory")]
public string? WorkingDirectoryYaml { get; set; }
[YamlMember(Alias = "serviceaccount")]
public ServiceAccount? ServiceAccountYaml { get; set; }
[YamlMember(Alias = "log")]
public YamlLog? YAMLLog { get; set; }
[YamlMember(Alias = "download")]
public List<YamlDownload>? DownloadsYaml { get; set; }
[YamlMember(Alias = "arguments")]
public string? ArgumentsYaml { get; set; }
[YamlMember(Alias = "startArguments")]
public string? StartArgumentsYaml { get; set; }
[YamlMember(Alias = "stopArguments")]
public string? StopArgumentsYaml { get; set; }
[YamlMember(Alias = "stopExecutable")]
public string? StopExecutableYaml { get; set; }
[YamlMember(Alias = "stopParentProcessFirst")]
public bool? StopParentProcessFirstYaml { get; set; }
[YamlMember(Alias = "resetFailureAfter")]
public string? ResetFailureAfterYaml { get; set; }
[YamlMember(Alias = "stopTimeout")]
public string? StopTimeoutYaml { get; set; }
[YamlMember(Alias = "startMode")]
public string? StartModeYaml { get; set; }
[YamlMember(Alias = "serviceDependencies")]
public string[]? ServiceDependenciesYaml { get; set; }
[YamlMember(Alias = "waitHint")]
public string? WaitHintYaml { get; set; }
[YamlMember(Alias = "sleepTime")]
public string? SleepTimeYaml { get; set; }
[YamlMember(Alias = "interactive")]
public bool? InteractiveYaml { get; set; }
[YamlMember(Alias = "priority")]
public string? PriorityYaml { get; set; }
[YamlMember(Alias = "beepOnShutdown")]
public bool BeepOnShutdown { get; set; }
[YamlMember(Alias = "env")]
public List<YamlEnv>? EnvironmentVariablesYaml { get; set; }
[YamlMember(Alias = "onFailure")]
public List<YamlFailureAction>? YamlFailureActions { get; set; }
[YamlMember(Alias = "delayedAutoStart")]
public bool DelayedAutoStart { get; set; }
[YamlMember(Alias = "securityDescriptor")]
public string? SecurityDescriptorYaml { get; set; }
[YamlMember(Alias = "extensions")]
public List<string>? YamlExtensionIds { get; set; }
public class YamlEnv
{
[YamlMember(Alias = "name")]
public string? Name { get; set; }
[YamlMember(Alias = "value")]
public string? Value { get; set; }
}
public class YamlLog : Log
{
private readonly YamlConfiguration configs;
public YamlLog()
{
this.configs = new YamlConfiguration();
}
[YamlMember(Alias = "mode")]
public string? ModeYamlLog { get; set; }
[YamlMember(Alias = "name")]
public string? NameYamlLog { get; set; }
[YamlMember(Alias = "sizeThreshold")]
public int? SizeThresholdYamlLog { get; set; }
[YamlMember(Alias = "keepFiles")]
public int? KeepFilesYamlLog { get; set; }
[YamlMember(Alias = "pattern")]
public string? PatternYamlLog { get; set; }
[YamlMember(Alias = "period")]
public int? PeriodYamlLog { get; set; }
[YamlMember(Alias = "logpath")]
public string? LogPathYamlLog { get; set; }
// Filters
[YamlMember(Alias = "outFileDisabled")]
public bool? OutFileDisabledYamlLog { get; set; }
[YamlMember(Alias = "errFileDisabled")]
public bool? ErrFileDisabledYamlLog { get; set; }
[YamlMember(Alias = "outFilePattern")]
public string? OutFilePatternYamlLog { get; set; }
[YamlMember(Alias = "errFilePattern")]
public string? ErrFilePatternYamlLog { get; set; }
// Zip options
[YamlMember(Alias = "autoRollAtTime")]
public string? AutoRollAtTimeYamlLog { get; set; }
[YamlMember(Alias = "zipOlderThanNumDays")]
public int? ZipOlderThanNumDaysYamlLog { get; set; }
[YamlMember(Alias = "zipDateFormat")]
public string? ZipDateFormatYamlLog { get; set; }
public override string Mode => this.ModeYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.Mode :
this.ModeYamlLog;
public override string Name
{
get
{
return this.NameYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.Name :
ExpandEnv(this.NameYamlLog);
}
}
public override string Directory
{
get
{
return this.LogPathYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.Directory :
ExpandEnv(this.LogPathYamlLog);
}
}
public override int? SizeThreshold
{
get
{
return this.SizeThresholdYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.SizeThreshold :
this.SizeThresholdYamlLog * RollingSizeTimeLogAppender.BytesPerKB;
}
}
public override int? KeepFiles
{
get
{
return this.KeepFilesYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.KeepFiles :
this.KeepFilesYamlLog;
}
}
public override string Pattern
{
get
{
if (this.PatternYamlLog != null)
{
return this.PatternYamlLog;
}
return DefaultWinSWSettings.DefaultLogSettings.Pattern;
}
}
public override int? Period => this.PeriodYamlLog is null ? 1 : this.PeriodYamlLog;
public override bool OutFileDisabled
{
get
{
return this.OutFileDisabledYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.OutFileDisabled :
(bool)this.OutFileDisabledYamlLog;
}
}
public override bool ErrFileDisabled
{
get
{
return this.ErrFileDisabledYamlLog is null ?
this.configs.Defaults.ErrFileDisabled :
(bool)this.ErrFileDisabledYamlLog;
}
}
public override string OutFilePattern
{
get
{
return this.OutFilePatternYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.OutFilePattern :
ExpandEnv(this.OutFilePatternYamlLog);
}
}
public override string ErrFilePattern
{
get
{
return this.ErrFilePatternYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.ErrFilePattern :
ExpandEnv(this.ErrFilePatternYamlLog);
}
}
public override string? AutoRollAtTime
{
get
{
return this.AutoRollAtTimeYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.AutoRollAtTime :
this.AutoRollAtTimeYamlLog;
}
}
public override int? ZipOlderThanNumDays
{
get
{
if (this.ZipOlderThanNumDaysYamlLog != null)
{
return this.ZipOlderThanNumDaysYamlLog;
}
return DefaultWinSWSettings.DefaultLogSettings.ZipOlderThanNumDays;
}
}
public override string? ZipDateFormat
{
get
{
return this.ZipDateFormatYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.ZipDateFormat :
this.ZipDateFormatYamlLog;
}
}
}
public class YamlDownload
{
[YamlMember(Alias = "from")]
public string FromYamlDownload { get; set; } = string.Empty;
[YamlMember(Alias = "to")]
public string ToYamlDownload { get; set; } = string.Empty;
[YamlMember(Alias = "auth")]
public string? AuthYamlDownload { get; set; }
[YamlMember(Alias = "username")]
public string? UsernameYamlDownload { get; set; }
[YamlMember(Alias = "password")]
public string? PasswordYamlDownload { get; set; }
[YamlMember(Alias = "unsecureAuth")]
public bool UnsecureAuthYamlDownload { get; set; }
[YamlMember(Alias = "failOnError")]
public bool FailOnErrorYamlDownload { get; set; }
[YamlMember(Alias = "proxy")]
public string? ProxyYamlDownload { get; set; }
public string FromDownload => ExpandEnv(this.FromYamlDownload);
public string ToDownload => ExpandEnv(this.ToYamlDownload);
public string? UsernameDownload => this.UsernameYamlDownload is null ? null : ExpandEnv(this.UsernameYamlDownload);
public string? PasswordDownload => this.PasswordYamlDownload is null ? null : ExpandEnv(this.PasswordYamlDownload);
public string? ProxyDownload => this.ProxyYamlDownload is null ? null : ExpandEnv(this.ProxyYamlDownload);
public AuthType AuthDownload
{
get
{
if (this.AuthYamlDownload is null)
{
return AuthType.None;
}
var auth = ExpandEnv(this.AuthYamlDownload);
try
{
return (AuthType)Enum.Parse(typeof(AuthType), auth, true);
}
catch
{
Console.WriteLine("Auth type in YAML must be one of the following:");
foreach (string at in Enum.GetNames(typeof(AuthType)))
{
Console.WriteLine(at);
}
throw;
}
}
}
}
public class YamlFailureAction
{
[YamlMember(Alias = "action")]
public string? FailureAction { get; set; }
[YamlMember(Alias = "delay")]
public string? FailureActionDelay { get; set; }
public SC_ACTION_TYPE Type
{
get
{
SC_ACTION_TYPE actionType = this.FailureAction switch
{
"restart" => SC_ACTION_TYPE.SC_ACTION_RESTART,
"none" => SC_ACTION_TYPE.SC_ACTION_NONE,
"reboot" => SC_ACTION_TYPE.SC_ACTION_REBOOT,
_ => throw new InvalidDataException("Invalid failure action: " + this.FailureAction)
};
return actionType;
}
}
public TimeSpan Delay => this.FailureActionDelay is null ? TimeSpan.Zero : ConfigHelper.ParseTimeSpan(this.FailureActionDelay);
}
private string? GetArguments(string? args, ArgType type)
{
if (args is null)
{
switch (type)
{
case ArgType.Arg:
return this.Defaults.Arguments;
case ArgType.Startarg:
return this.Defaults.StartArguments;
case ArgType.Stoparg:
return this.Defaults.StopArguments;
default:
return string.Empty;
}
}
return ExpandEnv(args);
}
private enum ArgType
{
Arg = 0,
Startarg = 1,
Stoparg = 2
}
private List<Download> GetDownloads(List<YamlDownload>? downloads)
{
if (downloads is null)
{
return this.Defaults.Downloads;
}
var result = new List<Download>(downloads.Count);
foreach (var item in downloads)
{
result.Add(new Download(
item.FromDownload,
item.ToDownload,
item.FailOnErrorYamlDownload,
item.AuthDownload,
item.UsernameDownload,
item.PasswordDownload,
item.UnsecureAuthYamlDownload,
item.ProxyDownload));
}
return result;
}
internal static string ExpandEnv(string str)
{
return Environment.ExpandEnvironmentVariables(str);
}
public string Id => this.IdYaml is null ? this.Defaults.Id : ExpandEnv(this.IdYaml);
public string Description => this.DescriptionYaml is null ? this.Defaults.Description : ExpandEnv(this.DescriptionYaml);
public string Executable => this.ExecutableYaml is null ? this.Defaults.Executable : ExpandEnv(this.ExecutableYaml);
public string ExecutablePath => this.ExecutablePathYaml is null ?
this.Defaults.ExecutablePath :
ExpandEnv(this.ExecutablePathYaml);
public string Caption => this.NameYaml is null ? this.Defaults.Caption : ExpandEnv(this.NameYaml);
public bool HideWindow => this.HideWindowYaml is null ? this.Defaults.HideWindow : (bool)this.HideWindowYaml;
public bool StopParentProcessFirst
{
get
{
return this.StopParentProcessFirstYaml is null ?
this.Defaults.StopParentProcessFirst :
(bool)this.StopParentProcessFirstYaml;
}
}
public StartMode StartMode
{
get
{
if (this.StartModeYaml is null)
{
return this.Defaults.StartMode;
}
var p = ExpandEnv(this.StartModeYaml);
try
{
return (StartMode)Enum.Parse(typeof(StartMode), p, true);
}
catch
{
Console.WriteLine("Start mode in YAML must be one of the following:");
foreach (string sm in Enum.GetNames(typeof(StartMode)))
{
Console.WriteLine(sm);
}
throw;
}
}
}
public string Arguments
{
get
{
var args = this.GetArguments(this.ArgumentsYaml, ArgType.Arg);
return args is null ? this.Defaults.Arguments : args;
}
}
public string? StartArguments => this.GetArguments(this.StartArgumentsYaml, ArgType.Startarg);
public string? StopArguments => this.GetArguments(this.StopArgumentsYaml, ArgType.Stoparg);
public string? StopExecutable
{
get
{
return this.StopExecutableYaml is null ?
this.Defaults.StopExecutable :
ExpandEnv(this.StopExecutableYaml);
}
}
public SC_ACTION[] FailureActions
{
get
{
if (this.YamlFailureActions is null)
{
return new SC_ACTION[0];
}
var arr = new List<SC_ACTION>();
foreach (var item in this.YamlFailureActions)
{
arr.Add(new SC_ACTION(item.Type, item.Delay));
}
return arr.ToArray();
}
}
public TimeSpan ResetFailureAfter => this.ResetFailureAfterYaml is null ?
this.Defaults.ResetFailureAfter :
ConfigHelper.ParseTimeSpan(this.ResetFailureAfterYaml);
public string WorkingDirectory => this.WorkingDirectoryYaml is null ?
this.Defaults.WorkingDirectory :
ExpandEnv(this.WorkingDirectoryYaml);
public ProcessPriorityClass Priority
{
get
{
if (this.PriorityYaml is null)
{
return this.Defaults.Priority;
}
var p = ExpandEnv(this.PriorityYaml);
try
{
return (ProcessPriorityClass)Enum.Parse(typeof(ProcessPriorityClass), p, true);
}
catch
{
Console.WriteLine("Priority in YAML must be one of the following:");
foreach (string pr in Enum.GetNames(typeof(ProcessPriorityClass)))
{
Console.WriteLine(pr);
}
throw;
}
}
}
public TimeSpan StopTimeout => this.StopTimeoutYaml is null ? this.Defaults.StopTimeout : ConfigHelper.ParseTimeSpan(this.StopTimeoutYaml);
public string[] ServiceDependencies
{
get
{
if (this.ServiceDependenciesYaml is null)
{
return this.Defaults.ServiceDependencies;
}
var result = new List<string>(0);
foreach (var item in this.ServiceDependenciesYaml)
{
result.Add(ExpandEnv(item));
}
return result.ToArray();
}
}
public TimeSpan WaitHint => this.WaitHintYaml is null ? this.Defaults.WaitHint : ConfigHelper.ParseTimeSpan(this.WaitHintYaml);
public TimeSpan SleepTime => this.SleepTimeYaml is null ? this.Defaults.SleepTime : ConfigHelper.ParseTimeSpan(this.SleepTimeYaml);
public bool Interactive => this.InteractiveYaml is null ? this.Defaults.Interactive : (bool)this.InteractiveYaml;
public List<Download> Downloads => this.GetDownloads(this.DownloadsYaml);
public Dictionary<string, string> EnvironmentVariables { get; set; } = new Dictionary<string, string>();
public void LoadEnvironmentVariables()
{
if (this.EnvironmentVariablesYaml is null)
{
this.EnvironmentVariables = this.Defaults.EnvironmentVariables;
}
else
{
foreach (var item in this.EnvironmentVariablesYaml)
{
if (item.Name is null || item.Value is null)
{
continue;
}
var key = item.Name;
var value = ExpandEnv(item.Value);
this.EnvironmentVariables[key] = value;
Environment.SetEnvironmentVariable(key, value);
}
}
}
public ServiceAccount ServiceAccount => this.ServiceAccountYaml is null ? this.Defaults.ServiceAccount : this.ServiceAccountYaml;
public Log Log => this.YAMLLog is null ? this.Defaults.Log : this.YAMLLog;
public string LogDirectory => this.Log.Directory;
public string LogMode => this.Log.Mode is null ? this.Defaults.LogMode : this.Log.Mode;
// TODO - Extensions
XmlNode? IWinSWConfiguration.ExtensionsConfiguration => throw new NotImplementedException();
public List<string> ExtensionIds => this.YamlExtensionIds ?? this.Defaults.ExtensionIds;
public string BaseName => this.Defaults.BaseName;
public string BasePath => this.Defaults.BasePath;
public string? SecurityDescriptor
{
get
{
if (this.SecurityDescriptorYaml is null)
{
return this.Defaults.SecurityDescriptor;
}
return ExpandEnv(this.SecurityDescriptorYaml);
}
}
}
}

560
src/Core/WinSWCore/Download.cs Executable file → Normal file
View File

@ -1,280 +1,280 @@
using System;
using System.IO;
using System.Net;
#if !VNEXT
using System.Reflection;
#endif
using System.Text;
#if VNEXT
using System.Threading.Tasks;
#endif
using System.Xml;
using log4net;
using WinSW.Util;
namespace WinSW
{
/// <summary>
/// Specify the download activities prior to the launch.
/// This enables self-updating services.
/// </summary>
public class Download
{
public enum AuthType
{
None = 0,
Sspi,
Basic
}
private static readonly ILog Logger = LogManager.GetLogger(typeof(Download));
public readonly string From;
public readonly string To;
public readonly AuthType Auth;
public readonly string? Username;
public readonly string? Password;
public readonly bool UnsecureAuth;
public readonly bool FailOnError;
public readonly string? Proxy;
public string ShortId => $"(download from {this.From})";
static Download()
{
#if NET461
// If your app runs on .NET Framework 4.7 or later versions, but targets an earlier version
AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", false);
#elif !VNEXT
// If your app runs on .NET Framework 4.6, but targets an earlier version
Type.GetType("System.AppContext")?.InvokeMember("SetSwitch", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] { "Switch.System.Net.DontEnableSchUseStrongCrypto", false });
const SecurityProtocolType Tls12 = (SecurityProtocolType)0x00000C00;
const SecurityProtocolType Tls11 = (SecurityProtocolType)0x00000300;
// Windows 7 and Windows Server 2008 R2
if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1)
{
try
{
ServicePointManager.SecurityProtocol |= Tls11 | Tls12;
Logger.Info("TLS 1.1/1.2 enabled");
}
catch (NotSupportedException)
{
Logger.Info("TLS 1.1/1.2 disabled");
}
}
#endif
}
// internal
public Download(
string from,
string to,
bool failOnError = false,
AuthType auth = AuthType.None,
string? username = null,
string? password = null,
bool unsecureAuth = false,
string? proxy = null)
{
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>
internal Download(XmlElement n)
{
this.From = XmlHelper.SingleAttribute<string>(n, "from");
this.To = XmlHelper.SingleAttribute<string>(n, "to");
// All arguments below are optional
this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false);
this.Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null);
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 (this.Auth == AuthType.Basic)
{
// Allow it only for HTTPS or for UnsecureAuth
if (!this.From.StartsWith("https:") && !this.UnsecureAuth)
{
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 (this.Username is null)
{
throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId);
}
if (this.Password is null)
{
throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId);
}
}
}
// Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest
private void SetBasicAuthHeader(WebRequest request, string username, string password)
{
string authInfo = username + ":" + password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
/// <summary>
/// Downloads the requested file and puts it to the specified target.
/// </summary>
/// <exception cref="WebException">
/// Download failure. FailOnError flag should be processed outside.
/// </exception>
#if VNEXT
public async Task PerformAsync()
#else
public void Perform()
#endif
{
WebRequest request = WebRequest.Create(this.From);
if (!string.IsNullOrEmpty(this.Proxy))
{
CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!);
if (proxyInformation.Credentials != null)
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials);
}
else
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress);
}
}
switch (this.Auth)
{
case AuthType.None:
// Do nothing
break;
case AuthType.Sspi:
request.UseDefaultCredentials = true;
request.PreAuthenticate = true;
request.Credentials = CredentialCache.DefaultCredentials;
break;
case AuthType.Basic:
this.SetBasicAuthHeader(request, this.Username!, this.Password!);
break;
default:
throw new WebException("Code defect. Unsupported authentication type: " + this.Auth);
}
bool supportsIfModifiedSince = false;
if (request is HttpWebRequest httpRequest && File.Exists(this.To))
{
supportsIfModifiedSince = true;
httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To);
}
DateTime lastModified = default;
string tmpFilePath = this.To + ".tmp";
try
{
#if VNEXT
using (WebResponse response = await request.GetResponseAsync())
#else
using (WebResponse response = request.GetResponse())
#endif
using (Stream responseStream = response.GetResponseStream())
using (FileStream tmpStream = new FileStream(tmpFilePath, FileMode.Create))
{
if (supportsIfModifiedSince)
{
lastModified = ((HttpWebResponse)response).LastModified;
}
#if VNEXT
await responseStream.CopyToAsync(tmpStream);
#elif NET20
CopyStream(responseStream, tmpStream);
#else
responseStream.CopyTo(tmpStream);
#endif
}
FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To);
if (supportsIfModifiedSince)
{
File.SetLastWriteTime(this.To, lastModified);
}
}
catch (WebException e)
{
if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified)
{
Logger.Info($"Skipped downloading unmodified resource '{this.From}'");
}
else
{
throw;
}
}
}
#if NET20
private static void CopyStream(Stream source, Stream destination)
{
byte[] buffer = new byte[8192];
int read;
while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
{
destination.Write(buffer, 0, read);
}
}
#endif
}
public class CustomProxyInformation
{
public string ServerAddress { get; set; }
public NetworkCredential? Credentials { get; set; }
public CustomProxyInformation(string proxy)
{
if (proxy.Contains("@"))
{
// Extract proxy credentials
int credsFrom = proxy.IndexOf("://") + 3;
int credsTo = proxy.LastIndexOf("@");
string completeCredsStr = proxy.Substring(credsFrom, credsTo - credsFrom);
int credsSeparator = completeCredsStr.IndexOf(":");
string username = completeCredsStr.Substring(0, credsSeparator);
string password = completeCredsStr.Substring(credsSeparator + 1);
this.Credentials = new NetworkCredential(username, password);
this.ServerAddress = proxy.Replace(completeCredsStr + "@", string.Empty);
}
else
{
this.ServerAddress = proxy;
}
}
}
}
using System;
using System.IO;
using System.Net;
#if !VNEXT
using System.Reflection;
#endif
using System.Text;
#if VNEXT
using System.Threading.Tasks;
#endif
using System.Xml;
using log4net;
using WinSW.Util;
namespace WinSW
{
/// <summary>
/// Specify the download activities prior to the launch.
/// This enables self-updating services.
/// </summary>
public class Download
{
public enum AuthType
{
None = 0,
Sspi,
Basic
}
private static readonly ILog Logger = LogManager.GetLogger(typeof(Download));
public readonly string From;
public readonly string To;
public readonly AuthType Auth;
public readonly string? Username;
public readonly string? Password;
public readonly bool UnsecureAuth;
public readonly bool FailOnError;
public readonly string? Proxy;
public string ShortId => $"(download from {this.From})";
static Download()
{
#if NET461
// If your app runs on .NET Framework 4.7 or later versions, but targets an earlier version
AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", false);
#elif !VNEXT
// If your app runs on .NET Framework 4.6, but targets an earlier version
Type.GetType("System.AppContext")?.InvokeMember("SetSwitch", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] { "Switch.System.Net.DontEnableSchUseStrongCrypto", false });
const SecurityProtocolType Tls12 = (SecurityProtocolType)0x00000C00;
const SecurityProtocolType Tls11 = (SecurityProtocolType)0x00000300;
// Windows 7 and Windows Server 2008 R2
if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1)
{
try
{
ServicePointManager.SecurityProtocol |= Tls11 | Tls12;
Logger.Info("TLS 1.1/1.2 enabled");
}
catch (NotSupportedException)
{
Logger.Info("TLS 1.1/1.2 disabled");
}
}
#endif
}
// internal
public Download(
string from,
string to,
bool failOnError = false,
AuthType auth = AuthType.None,
string? username = null,
string? password = null,
bool unsecureAuth = false,
string? proxy = null)
{
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>
internal Download(XmlElement n)
{
this.From = XmlHelper.SingleAttribute<string>(n, "from");
this.To = XmlHelper.SingleAttribute<string>(n, "to");
// All arguments below are optional
this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false);
this.Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null);
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 (this.Auth == AuthType.Basic)
{
// Allow it only for HTTPS or for UnsecureAuth
if (!this.From.StartsWith("https:") && !this.UnsecureAuth)
{
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 (this.Username is null)
{
throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId);
}
if (this.Password is null)
{
throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId);
}
}
}
// Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest
private void SetBasicAuthHeader(WebRequest request, string username, string password)
{
string authInfo = username + ":" + password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
/// <summary>
/// Downloads the requested file and puts it to the specified target.
/// </summary>
/// <exception cref="WebException">
/// Download failure. FailOnError flag should be processed outside.
/// </exception>
#if VNEXT
public async Task PerformAsync()
#else
public void Perform()
#endif
{
WebRequest request = WebRequest.Create(this.From);
if (!string.IsNullOrEmpty(this.Proxy))
{
CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!);
if (proxyInformation.Credentials != null)
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials);
}
else
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress);
}
}
switch (this.Auth)
{
case AuthType.None:
// Do nothing
break;
case AuthType.Sspi:
request.UseDefaultCredentials = true;
request.PreAuthenticate = true;
request.Credentials = CredentialCache.DefaultCredentials;
break;
case AuthType.Basic:
this.SetBasicAuthHeader(request, this.Username!, this.Password!);
break;
default:
throw new WebException("Code defect. Unsupported authentication type: " + this.Auth);
}
bool supportsIfModifiedSince = false;
if (request is HttpWebRequest httpRequest && File.Exists(this.To))
{
supportsIfModifiedSince = true;
httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To);
}
DateTime lastModified = default;
string tmpFilePath = this.To + ".tmp";
try
{
#if VNEXT
using (WebResponse response = await request.GetResponseAsync())
#else
using (WebResponse response = request.GetResponse())
#endif
using (Stream responseStream = response.GetResponseStream())
using (FileStream tmpStream = new FileStream(tmpFilePath, FileMode.Create))
{
if (supportsIfModifiedSince)
{
lastModified = ((HttpWebResponse)response).LastModified;
}
#if VNEXT
await responseStream.CopyToAsync(tmpStream);
#elif NET20
CopyStream(responseStream, tmpStream);
#else
responseStream.CopyTo(tmpStream);
#endif
}
FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To);
if (supportsIfModifiedSince)
{
File.SetLastWriteTime(this.To, lastModified);
}
}
catch (WebException e)
{
if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified)
{
Logger.Info($"Skipped downloading unmodified resource '{this.From}'");
}
else
{
throw;
}
}
}
#if NET20
private static void CopyStream(Stream source, Stream destination)
{
byte[] buffer = new byte[8192];
int read;
while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
{
destination.Write(buffer, 0, read);
}
}
#endif
}
public class CustomProxyInformation
{
public string ServerAddress { get; set; }
public NetworkCredential? Credentials { get; set; }
public CustomProxyInformation(string proxy)
{
if (proxy.Contains("@"))
{
// Extract proxy credentials
int credsFrom = proxy.IndexOf("://") + 3;
int credsTo = proxy.LastIndexOf("@");
string completeCredsStr = proxy.Substring(credsFrom, credsTo - credsFrom);
int credsSeparator = completeCredsStr.IndexOf(":");
string username = completeCredsStr.Substring(0, credsSeparator);
string password = completeCredsStr.Substring(credsSeparator + 1);
this.Credentials = new NetworkCredential(username, password);
this.ServerAddress = proxy.Replace(completeCredsStr + "@", string.Empty);
}
else
{
this.ServerAddress = proxy;
}
}
}
}

View File

@ -1,205 +1,205 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace DynamicProxy
{
/// <summary>
/// Interface that a user defined proxy handler needs to implement. This interface
/// defines one method that gets invoked by the generated proxy.
/// </summary>
public interface IProxyInvocationHandler
{
/// <param name="proxy">The instance of the proxy</param>
/// <param name="method">The method info that can be used to invoke the actual method on the object implementation</param>
/// <param name="parameters">Parameters to pass to the method</param>
/// <returns>Object</returns>
object? Invoke(object proxy, MethodInfo method, object[] parameters);
}
/// <summary>
/// </summary>
public static class ProxyFactory
{
private const string ProxySuffix = "Proxy";
private const string AssemblyName = "ProxyAssembly";
private const string ModuleName = "ProxyModule";
private const string HandlerName = "handler";
private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();
private static readonly AssemblyBuilder AssemblyBuilder =
#if VNEXT
AssemblyBuilder.DefineDynamicAssembly(
#else
AppDomain.CurrentDomain.DefineDynamicAssembly(
#endif
new AssemblyName(AssemblyName), AssemblyBuilderAccess.Run);
private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder.DefineDynamicModule(ModuleName);
public static object Create(IProxyInvocationHandler handler, Type objType, bool isObjInterface = false)
{
string typeName = objType.FullName + ProxySuffix;
Type? type = null;
lock (TypeCache)
{
if (!TypeCache.TryGetValue(typeName, out type))
{
type = CreateType(typeName, isObjInterface ? new Type[] { objType } : objType.GetInterfaces());
TypeCache.Add(typeName, type);
}
}
return Activator.CreateInstance(type, new object[] { handler })!;
}
private static Type CreateType(string dynamicTypeName, Type[] interfaces)
{
Type objType = typeof(object);
Type handlerType = typeof(IProxyInvocationHandler);
TypeAttributes typeAttributes = TypeAttributes.Public | TypeAttributes.Sealed;
// Gather up the proxy information and create a new type builder. One that
// inherits from Object and implements the interface passed in
TypeBuilder typeBuilder = ModuleBuilder.DefineType(
dynamicTypeName, typeAttributes, objType, interfaces);
// Define a member variable to hold the delegate
FieldBuilder handlerField = typeBuilder.DefineField(
HandlerName, handlerType, FieldAttributes.Private | FieldAttributes.InitOnly);
// build a constructor that takes the delegate object as the only argument
ConstructorInfo baseConstructor = objType.GetConstructor(Type.EmptyTypes)!;
ConstructorBuilder delegateConstructor = typeBuilder.DefineConstructor(
MethodAttributes.Public, CallingConventions.Standard, new Type[] { handlerType });
ILGenerator constructorIL = delegateConstructor.GetILGenerator();
// 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);
// for every method that the interfaces define, build a corresponding
// method in the dynamic type that calls the handlers invoke method.
foreach (Type interfaceType in interfaces)
{
GenerateMethod(interfaceType, handlerField, typeBuilder);
}
return typeBuilder.CreateType()!;
}
/// <summary>
/// <see cref="IProxyInvocationHandler.Invoke(object, MethodInfo, object[])"/>.
/// </summary>
private static readonly MethodInfo InvokeMethod = typeof(IProxyInvocationHandler).GetMethod(nameof(IProxyInvocationHandler.Invoke))!;
/// <summary>
/// <see cref="MethodBase.GetMethodFromHandle(RuntimeMethodHandle)"/>.
/// </summary>
private static readonly MethodInfo GetMethodFromHandleMethod = typeof(MethodBase).GetMethod(nameof(MethodBase.GetMethodFromHandle), new[] { typeof(RuntimeMethodHandle) })!;
private static void GenerateMethod(Type interfaceType, FieldBuilder handlerField, TypeBuilder typeBuilder)
{
MethodInfo[] interfaceMethods = interfaceType.GetMethods();
for (int i = 0; i < interfaceMethods.Length; i++)
{
MethodInfo methodInfo = interfaceMethods[i];
// Get the method parameters since we need to create an array
// of parameter types
ParameterInfo[] methodParams = methodInfo.GetParameters();
int numOfParams = methodParams.Length;
Type[] methodParameters = new Type[numOfParams];
// convert the ParameterInfo objects into Type
for (int j = 0; j < numOfParams; j++)
{
methodParameters[j] = methodParams[j].ParameterType;
}
// create a new builder for the method in the interface
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
methodInfo.Name,
/*MethodAttributes.Public | MethodAttributes.Virtual | */ methodInfo.Attributes & ~MethodAttributes.Abstract,
CallingConventions.Standard,
methodInfo.ReturnType,
methodParameters);
ILGenerator methodIL = methodBuilder.GetILGenerator();
// invoke target: IProxyInvocationHandler
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldfld, handlerField);
// 1st parameter: object proxy
methodIL.Emit(OpCodes.Ldarg_0);
// 2nd parameter: MethodInfo method
methodIL.Emit(OpCodes.Ldtoken, methodInfo);
methodIL.Emit(OpCodes.Call, GetMethodFromHandleMethod);
methodIL.Emit(OpCodes.Castclass, typeof(MethodInfo));
// 3rd parameter: object[] parameters
methodIL.Emit(OpCodes.Ldc_I4, numOfParams);
methodIL.Emit(OpCodes.Newarr, typeof(object));
// if we have any parameters, then iterate through and set the values
// of each element to the corresponding arguments
for (int j = 0; j < numOfParams; j++)
{
methodIL.Emit(OpCodes.Dup); // copy the array
methodIL.Emit(OpCodes.Ldc_I4, j);
methodIL.Emit(OpCodes.Ldarg, j + 1); // +1 for "this"
if (methodParameters[j].IsValueType)
{
methodIL.Emit(OpCodes.Box, methodParameters[j]);
}
methodIL.Emit(OpCodes.Stelem_Ref);
}
// call the Invoke method
methodIL.Emit(OpCodes.Callvirt, InvokeMethod);
if (methodInfo.ReturnType != typeof(void))
{
methodIL.Emit(OpCodes.Unbox_Any, methodInfo.ReturnType);
}
else
{
// pop the return value that Invoke returned from the stack since
// the method's return type is void.
methodIL.Emit(OpCodes.Pop);
}
// Return
methodIL.Emit(OpCodes.Ret);
}
// Iterate through the parent interfaces and recursively call this method
foreach (Type parentType in interfaceType.GetInterfaces())
{
GenerateMethod(parentType, handlerField, typeBuilder);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace DynamicProxy
{
/// <summary>
/// Interface that a user defined proxy handler needs to implement. This interface
/// defines one method that gets invoked by the generated proxy.
/// </summary>
public interface IProxyInvocationHandler
{
/// <param name="proxy">The instance of the proxy</param>
/// <param name="method">The method info that can be used to invoke the actual method on the object implementation</param>
/// <param name="parameters">Parameters to pass to the method</param>
/// <returns>Object</returns>
object? Invoke(object proxy, MethodInfo method, object[] parameters);
}
/// <summary>
/// </summary>
public static class ProxyFactory
{
private const string ProxySuffix = "Proxy";
private const string AssemblyName = "ProxyAssembly";
private const string ModuleName = "ProxyModule";
private const string HandlerName = "handler";
private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();
private static readonly AssemblyBuilder AssemblyBuilder =
#if VNEXT
AssemblyBuilder.DefineDynamicAssembly(
#else
AppDomain.CurrentDomain.DefineDynamicAssembly(
#endif
new AssemblyName(AssemblyName), AssemblyBuilderAccess.Run);
private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder.DefineDynamicModule(ModuleName);
public static object Create(IProxyInvocationHandler handler, Type objType, bool isObjInterface = false)
{
string typeName = objType.FullName + ProxySuffix;
Type? type = null;
lock (TypeCache)
{
if (!TypeCache.TryGetValue(typeName, out type))
{
type = CreateType(typeName, isObjInterface ? new Type[] { objType } : objType.GetInterfaces());
TypeCache.Add(typeName, type);
}
}
return Activator.CreateInstance(type, new object[] { handler })!;
}
private static Type CreateType(string dynamicTypeName, Type[] interfaces)
{
Type objType = typeof(object);
Type handlerType = typeof(IProxyInvocationHandler);
TypeAttributes typeAttributes = TypeAttributes.Public | TypeAttributes.Sealed;
// Gather up the proxy information and create a new type builder. One that
// inherits from Object and implements the interface passed in
TypeBuilder typeBuilder = ModuleBuilder.DefineType(
dynamicTypeName, typeAttributes, objType, interfaces);
// Define a member variable to hold the delegate
FieldBuilder handlerField = typeBuilder.DefineField(
HandlerName, handlerType, FieldAttributes.Private | FieldAttributes.InitOnly);
// build a constructor that takes the delegate object as the only argument
ConstructorInfo baseConstructor = objType.GetConstructor(Type.EmptyTypes)!;
ConstructorBuilder delegateConstructor = typeBuilder.DefineConstructor(
MethodAttributes.Public, CallingConventions.Standard, new Type[] { handlerType });
ILGenerator constructorIL = delegateConstructor.GetILGenerator();
// 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);
// for every method that the interfaces define, build a corresponding
// method in the dynamic type that calls the handlers invoke method.
foreach (Type interfaceType in interfaces)
{
GenerateMethod(interfaceType, handlerField, typeBuilder);
}
return typeBuilder.CreateType()!;
}
/// <summary>
/// <see cref="IProxyInvocationHandler.Invoke(object, MethodInfo, object[])"/>.
/// </summary>
private static readonly MethodInfo InvokeMethod = typeof(IProxyInvocationHandler).GetMethod(nameof(IProxyInvocationHandler.Invoke))!;
/// <summary>
/// <see cref="MethodBase.GetMethodFromHandle(RuntimeMethodHandle)"/>.
/// </summary>
private static readonly MethodInfo GetMethodFromHandleMethod = typeof(MethodBase).GetMethod(nameof(MethodBase.GetMethodFromHandle), new[] { typeof(RuntimeMethodHandle) })!;
private static void GenerateMethod(Type interfaceType, FieldBuilder handlerField, TypeBuilder typeBuilder)
{
MethodInfo[] interfaceMethods = interfaceType.GetMethods();
for (int i = 0; i < interfaceMethods.Length; i++)
{
MethodInfo methodInfo = interfaceMethods[i];
// Get the method parameters since we need to create an array
// of parameter types
ParameterInfo[] methodParams = methodInfo.GetParameters();
int numOfParams = methodParams.Length;
Type[] methodParameters = new Type[numOfParams];
// convert the ParameterInfo objects into Type
for (int j = 0; j < numOfParams; j++)
{
methodParameters[j] = methodParams[j].ParameterType;
}
// create a new builder for the method in the interface
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
methodInfo.Name,
/*MethodAttributes.Public | MethodAttributes.Virtual | */ methodInfo.Attributes & ~MethodAttributes.Abstract,
CallingConventions.Standard,
methodInfo.ReturnType,
methodParameters);
ILGenerator methodIL = methodBuilder.GetILGenerator();
// invoke target: IProxyInvocationHandler
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldfld, handlerField);
// 1st parameter: object proxy
methodIL.Emit(OpCodes.Ldarg_0);
// 2nd parameter: MethodInfo method
methodIL.Emit(OpCodes.Ldtoken, methodInfo);
methodIL.Emit(OpCodes.Call, GetMethodFromHandleMethod);
methodIL.Emit(OpCodes.Castclass, typeof(MethodInfo));
// 3rd parameter: object[] parameters
methodIL.Emit(OpCodes.Ldc_I4, numOfParams);
methodIL.Emit(OpCodes.Newarr, typeof(object));
// if we have any parameters, then iterate through and set the values
// of each element to the corresponding arguments
for (int j = 0; j < numOfParams; j++)
{
methodIL.Emit(OpCodes.Dup); // copy the array
methodIL.Emit(OpCodes.Ldc_I4, j);
methodIL.Emit(OpCodes.Ldarg, j + 1); // +1 for "this"
if (methodParameters[j].IsValueType)
{
methodIL.Emit(OpCodes.Box, methodParameters[j]);
}
methodIL.Emit(OpCodes.Stelem_Ref);
}
// call the Invoke method
methodIL.Emit(OpCodes.Callvirt, InvokeMethod);
if (methodInfo.ReturnType != typeof(void))
{
methodIL.Emit(OpCodes.Unbox_Any, methodInfo.ReturnType);
}
else
{
// pop the return value that Invoke returned from the stack since
// the method's return type is void.
methodIL.Emit(OpCodes.Pop);
}
// Return
methodIL.Emit(OpCodes.Ret);
}
// Iterate through the parent interfaces and recursively call this method
foreach (Type parentType in interfaceType.GetInterfaces())
{
GenerateMethod(parentType, handlerField, typeBuilder);
}
}
}
}

View File

@ -1,4 +1,5 @@
using System.Xml;
using WinSW.Configuration;
namespace WinSW.Extensions
{
@ -10,7 +11,7 @@ namespace WinSW.Extensions
public WinSWExtensionDescriptor Descriptor { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public virtual void Configure(ServiceDescriptor descriptor, XmlNode node)
public virtual void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
// Do nothing
}

View File

@ -1,4 +1,5 @@
using System.Xml;
using WinSW.Configuration;
namespace WinSW.Extensions
{
@ -27,7 +28,7 @@ namespace WinSW.Extensions
/// </summary>
/// <param name="descriptor">Service descriptor</param>
/// <param name="node">Configuration node</param>
void Configure(ServiceDescriptor descriptor, XmlNode node);
void Configure(IWinSWConfiguration descriptor, XmlNode node);
/// <summary>
/// Start handler. Called during startup of the service before the child process.

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Xml;
using log4net;
using WinSW.Configuration;
namespace WinSW.Extensions
{
@ -9,11 +10,11 @@ namespace WinSW.Extensions
{
public Dictionary<string, IWinSWExtension> Extensions { get; private set; }
public ServiceDescriptor ServiceDescriptor { get; private set; }
public IWinSWConfiguration ServiceDescriptor { get; private set; }
private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager));
public WinSWExtensionManager(ServiceDescriptor serviceDescriptor)
public WinSWExtensionManager(IWinSWConfiguration serviceDescriptor)
{
this.ServiceDescriptor = serviceDescriptor;
this.Extensions = new Dictionary<string, IWinSWExtension>();

File diff suppressed because it is too large Load Diff

22
src/Core/WinSWCore/Native/Kernel32.cs Executable file → Normal file
View File

@ -1,11 +1,11 @@
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace WinSW.Native
{
internal static class Kernel32
{
[DllImport(Libraries.Kernel32)]
internal static extern bool SetStdHandle(int stdHandle, SafeFileHandle handle);
}
}
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace WinSW.Native
{
internal static class Kernel32
{
[DllImport(Libraries.Kernel32)]
internal static extern bool SetStdHandle(int stdHandle, SafeFileHandle handle);
}
}

View File

@ -1,108 +1,108 @@
using System;
namespace WinSW
{
// 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;
public PeriodicRollingCalendar(string format, long period)
{
this.format = format;
this.period = period;
this.currentRoll = DateTime.Now;
}
public void Init()
{
this.PeriodicityType = this.DeterminePeriodicityType();
this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period);
}
public enum Periodicity
{
ERRONEOUS,
TOP_OF_MILLISECOND,
TOP_OF_SECOND,
TOP_OF_MINUTE,
TOP_OF_HOUR,
TOP_OF_DAY
}
private static readonly Periodicity[] ValidOrderedList =
{
Periodicity.TOP_OF_MILLISECOND, Periodicity.TOP_OF_SECOND, Periodicity.TOP_OF_MINUTE, Periodicity.TOP_OF_HOUR, Periodicity.TOP_OF_DAY
};
private Periodicity DeterminePeriodicityType()
{
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.format, this.period);
DateTime epoch = new DateTime(1970, 1, 1);
foreach (Periodicity i in ValidOrderedList)
{
string r0 = epoch.ToString(this.format);
periodicRollingCalendar.PeriodicityType = i;
DateTime next = periodicRollingCalendar.NextTriggeringTime(epoch, 1);
string r1 = next.ToString(this.format);
if (r0 != r1)
{
return i;
}
}
return Periodicity.ERRONEOUS;
}
private DateTime NextTriggeringTime(DateTime input, long increment) => this.PeriodicityType switch
{
Periodicity.TOP_OF_MILLISECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond)
.AddMilliseconds(increment),
Periodicity.TOP_OF_SECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second)
.AddSeconds(increment),
Periodicity.TOP_OF_MINUTE =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, 0)
.AddMinutes(increment),
Periodicity.TOP_OF_HOUR =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0)
.AddHours(increment),
Periodicity.TOP_OF_DAY =>
new DateTime(input.Year, input.Month, input.Day)
.AddDays(increment),
_ => throw new Exception("invalid periodicity type: " + this.PeriodicityType),
};
public Periodicity PeriodicityType { get; set; }
public bool ShouldRoll
{
get
{
DateTime now = DateTime.Now;
if (now > this.nextRoll)
{
this.currentRoll = now;
this.nextRoll = this.NextTriggeringTime(now, this.period);
return true;
}
return false;
}
}
public string Format => this.currentRoll.ToString(this.format);
}
}
using System;
namespace WinSW
{
// 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;
public PeriodicRollingCalendar(string format, long period)
{
this.format = format;
this.period = period;
this.currentRoll = DateTime.Now;
}
public void Init()
{
this.PeriodicityType = this.DeterminePeriodicityType();
this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period);
}
public enum Periodicity
{
ERRONEOUS,
TOP_OF_MILLISECOND,
TOP_OF_SECOND,
TOP_OF_MINUTE,
TOP_OF_HOUR,
TOP_OF_DAY
}
private static readonly Periodicity[] ValidOrderedList =
{
Periodicity.TOP_OF_MILLISECOND, Periodicity.TOP_OF_SECOND, Periodicity.TOP_OF_MINUTE, Periodicity.TOP_OF_HOUR, Periodicity.TOP_OF_DAY
};
private Periodicity DeterminePeriodicityType()
{
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.format, this.period);
DateTime epoch = new DateTime(1970, 1, 1);
foreach (Periodicity i in ValidOrderedList)
{
string r0 = epoch.ToString(this.format);
periodicRollingCalendar.PeriodicityType = i;
DateTime next = periodicRollingCalendar.NextTriggeringTime(epoch, 1);
string r1 = next.ToString(this.format);
if (r0 != r1)
{
return i;
}
}
return Periodicity.ERRONEOUS;
}
private DateTime NextTriggeringTime(DateTime input, long increment) => this.PeriodicityType switch
{
Periodicity.TOP_OF_MILLISECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond)
.AddMilliseconds(increment),
Periodicity.TOP_OF_SECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second)
.AddSeconds(increment),
Periodicity.TOP_OF_MINUTE =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, 0)
.AddMinutes(increment),
Periodicity.TOP_OF_HOUR =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0)
.AddHours(increment),
Periodicity.TOP_OF_DAY =>
new DateTime(input.Year, input.Month, input.Day)
.AddDays(increment),
_ => throw new Exception("invalid periodicity type: " + this.PeriodicityType),
};
public Periodicity PeriodicityType { get; set; }
public bool ShouldRoll
{
get
{
DateTime now = DateTime.Now;
if (now > this.nextRoll)
{
this.currentRoll = now;
this.nextRoll = this.NextTriggeringTime(now, this.period);
return true;
}
return false;
}
}
public string Format => this.currentRoll.ToString(this.format);
}
}

1479
src/Core/WinSWCore/ServiceDescriptor.cs Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
using System;
using System.IO;
using WinSW.Configuration;
using YamlDotNet.Serialization;
namespace WinSW
{
public class ServiceDescriptorYaml
{
public readonly YamlConfiguration Configurations = new YamlConfiguration();
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
public ServiceDescriptorYaml()
{
string p = Defaults.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 + ".yml")))
{
break;
}
if (d.Parent is null)
{
throw new FileNotFoundException("Unable to locate " + baseName + ".yml file within executable directory or any parents");
}
d = d.Parent;
}
var basepath = Path.Combine(d.FullName, baseName);
using (var reader = new StreamReader(basepath + ".yml"))
{
var file = reader.ReadToEnd();
var deserializer = new DeserializerBuilder().Build();
this.Configurations = deserializer.Deserialize<YamlConfiguration>(file);
}
Environment.SetEnvironmentVariable("BASE", d.FullName);
// ditto for ID
Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Id);
// New name
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, Defaults.ExecutablePath);
// Also inject system environment variables
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Id);
this.Configurations.LoadEnvironmentVariables();
}
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public ServiceDescriptorYaml(YamlConfiguration configs)
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
{
this.Configurations = configs;
this.Configurations.LoadEnvironmentVariables();
}
public static ServiceDescriptorYaml FromYaml(string yaml)
{
var deserializer = new DeserializerBuilder().Build();
var configs = deserializer.Deserialize<YamlConfiguration>(yaml);
return new ServiceDescriptorYaml(configs);
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
namespace WinSW.Util
{
public static class ConfigHelper
{
public static TimeSpan ParseTimeSpan(string v)
{
v = v.Trim();
foreach (var s in Suffix)
{
if (v.EndsWith(s.Key))
{
return TimeSpan.FromMilliseconds(int.Parse(v.Substring(0, v.Length - s.Key.Length).Trim()) * s.Value);
}
}
return TimeSpan.FromMilliseconds(int.Parse(v));
}
private static readonly Dictionary<string, long> Suffix = new Dictionary<string, long>
{
{ "ms", 1 },
{ "sec", 1000L },
{ "secs", 1000L },
{ "min", 1000L * 60L },
{ "mins", 1000L * 60L },
{ "hr", 1000L * 60L * 60L },
{ "hrs", 1000L * 60L * 60L },
{ "hour", 1000L * 60L * 60L },
{ "hours", 1000L * 60L * 60L },
{ "day", 1000L * 60L * 60L * 24L },
{ "days", 1000L * 60L * 60L * 24L }
};
}
}

View File

@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="log4net" Version="2.0.8" />
<PackageReference Include="YamlDotNet" Version="8.1.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>

454
src/Core/WinSWCore/Wmi.cs Executable file → Normal file
View File

@ -1,227 +1,227 @@
using System;
using System.Management;
using System.Reflection;
using System.Text;
using DynamicProxy;
namespace WMI
{
// https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-service
public enum ReturnValue : uint
{
Success = 0,
NotSupported = 1,
AccessDenied = 2,
DependentServicesRunning = 3,
InvalidServiceControl = 4,
ServiceCannotAcceptControl = 5,
ServiceNotActive = 6,
ServiceRequestTimeout = 7,
UnknownFailure = 8,
PathNotFound = 9,
ServiceAlreadyRunning = 10,
ServiceDatabaseLocked = 11,
ServiceDependencyDeleted = 12,
ServiceDependencyFailure = 13,
ServiceDisabled = 14,
ServiceLogonFailure = 15,
ServiceMarkedForDeletion = 16,
ServiceNoThread = 17,
StatusCircularDependency = 18,
StatusDuplicateName = 19,
StatusInvalidName = 20,
StatusInvalidParameter = 21,
StatusInvalidServiceAccount = 22,
StatusServiceExists = 23,
ServiceAlreadyPaused = 24,
NoSuchService = 200
}
/// <summary>
/// Signals a problem in WMI related operations
/// </summary>
public class WmiException : Exception
{
public readonly ReturnValue ErrorCode;
public WmiException(string message, ReturnValue code)
: base(message)
{
this.ErrorCode = code;
}
public WmiException(ReturnValue code)
: this(code.ToString(), code)
{
}
}
/// <summary>
/// Associated a WMI class name to the proxy interface (which should extend from IWmiCollection)
/// </summary>
public class WmiClassName : Attribute
{
public readonly string Name;
public WmiClassName(string name) => this.Name = name;
}
/// <summary>
/// Marker interface to denote a collection in WMI.
/// </summary>
public interface IWmiCollection
{
}
/// <summary>
/// Marker interface to denote an individual managed object
/// </summary>
public interface IWmiObject
{
}
public sealed class WmiRoot
{
private readonly ManagementScope wmiScope;
public WmiRoot()
{
ConnectionOptions options = new ConnectionOptions
{
EnablePrivileges = true,
Impersonation = ImpersonationLevel.Impersonate,
Authentication = AuthenticationLevel.PacketPrivacy,
};
this.wmiScope = new ManagementScope(@"\\.\root\cimv2", options);
this.wmiScope.Connect();
}
private static string Capitalize(string s)
{
return char.ToUpper(s[0]) + s.Substring(1);
}
private abstract class BaseHandler : IProxyInvocationHandler
{
public abstract object? Invoke(object proxy, MethodInfo method, object[] arguments);
protected void CheckError(ManagementBaseObject result)
{
uint code = (uint)result["returnValue"];
if (code != 0)
{
throw new WmiException((ReturnValue)code);
}
}
protected ManagementBaseObject GetMethodParameters(ManagementObject wmiObject, string methodName, ParameterInfo[] methodParameters, object[] arguments)
{
ManagementBaseObject wmiParameters = wmiObject.GetMethodParameters(methodName);
for (int i = 0; i < arguments.Length; i++)
{
string capitalizedName = Capitalize(methodParameters[i].Name!);
wmiParameters[capitalizedName] = arguments[i];
}
return wmiParameters;
}
}
private class InstanceHandler : BaseHandler, IWmiObject
{
private readonly ManagementObject wmiObject;
public InstanceHandler(ManagementObject wmiObject) => this.wmiObject = wmiObject;
public override object? Invoke(object proxy, MethodInfo method, object[] arguments)
{
if (method.DeclaringType == typeof(IWmiObject))
{
return method.Invoke(this, arguments);
}
// TODO: proper property support
if (method.Name.StartsWith("set_"))
{
this.wmiObject[method.Name.Substring(4)] = arguments[0];
return null;
}
if (method.Name.StartsWith("get_"))
{
return this.wmiObject[method.Name.Substring(4)];
}
string methodName = method.Name;
using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null :
this.GetMethodParameters(this.wmiObject, methodName, method.GetParameters(), arguments);
using ManagementBaseObject result = this.wmiObject.InvokeMethod(methodName, wmiParameters, null);
this.CheckError(result);
return null;
}
}
private class ClassHandler : BaseHandler
{
private readonly ManagementClass wmiClass;
private readonly string className;
public ClassHandler(ManagementScope wmiScope, string className)
{
this.wmiClass = new ManagementClass(wmiScope, new ManagementPath(className), null);
this.className = className;
}
public override object? Invoke(object proxy, MethodInfo method, object[] arguments)
{
ParameterInfo[] methodParameters = method.GetParameters();
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)
{
return ProxyFactory.Create(new InstanceHandler(wmiObject), method.ReturnType, true);
}
return null;
}
string methodName = method.Name;
using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null :
this.GetMethodParameters(this.wmiClass, methodName, methodParameters, arguments);
using ManagementBaseObject result = this.wmiClass.InvokeMethod(methodName, wmiParameters, null);
this.CheckError(result);
return null;
}
}
/// <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
{
WmiClassName className = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0];
return (T)ProxyFactory.Create(new ClassHandler(this.wmiScope, className.Name), typeof(T), true);
}
}
}
using System;
using System.Management;
using System.Reflection;
using System.Text;
using DynamicProxy;
namespace WMI
{
// https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-service
public enum ReturnValue : uint
{
Success = 0,
NotSupported = 1,
AccessDenied = 2,
DependentServicesRunning = 3,
InvalidServiceControl = 4,
ServiceCannotAcceptControl = 5,
ServiceNotActive = 6,
ServiceRequestTimeout = 7,
UnknownFailure = 8,
PathNotFound = 9,
ServiceAlreadyRunning = 10,
ServiceDatabaseLocked = 11,
ServiceDependencyDeleted = 12,
ServiceDependencyFailure = 13,
ServiceDisabled = 14,
ServiceLogonFailure = 15,
ServiceMarkedForDeletion = 16,
ServiceNoThread = 17,
StatusCircularDependency = 18,
StatusDuplicateName = 19,
StatusInvalidName = 20,
StatusInvalidParameter = 21,
StatusInvalidServiceAccount = 22,
StatusServiceExists = 23,
ServiceAlreadyPaused = 24,
NoSuchService = 200
}
/// <summary>
/// Signals a problem in WMI related operations
/// </summary>
public class WmiException : Exception
{
public readonly ReturnValue ErrorCode;
public WmiException(string message, ReturnValue code)
: base(message)
{
this.ErrorCode = code;
}
public WmiException(ReturnValue code)
: this(code.ToString(), code)
{
}
}
/// <summary>
/// Associated a WMI class name to the proxy interface (which should extend from IWmiCollection)
/// </summary>
public class WmiClassName : Attribute
{
public readonly string Name;
public WmiClassName(string name) => this.Name = name;
}
/// <summary>
/// Marker interface to denote a collection in WMI.
/// </summary>
public interface IWmiCollection
{
}
/// <summary>
/// Marker interface to denote an individual managed object
/// </summary>
public interface IWmiObject
{
}
public sealed class WmiRoot
{
private readonly ManagementScope wmiScope;
public WmiRoot()
{
ConnectionOptions options = new ConnectionOptions
{
EnablePrivileges = true,
Impersonation = ImpersonationLevel.Impersonate,
Authentication = AuthenticationLevel.PacketPrivacy,
};
this.wmiScope = new ManagementScope(@"\\.\root\cimv2", options);
this.wmiScope.Connect();
}
private static string Capitalize(string s)
{
return char.ToUpper(s[0]) + s.Substring(1);
}
private abstract class BaseHandler : IProxyInvocationHandler
{
public abstract object? Invoke(object proxy, MethodInfo method, object[] arguments);
protected void CheckError(ManagementBaseObject result)
{
uint code = (uint)result["returnValue"];
if (code != 0)
{
throw new WmiException((ReturnValue)code);
}
}
protected ManagementBaseObject GetMethodParameters(ManagementObject wmiObject, string methodName, ParameterInfo[] methodParameters, object[] arguments)
{
ManagementBaseObject wmiParameters = wmiObject.GetMethodParameters(methodName);
for (int i = 0; i < arguments.Length; i++)
{
string capitalizedName = Capitalize(methodParameters[i].Name!);
wmiParameters[capitalizedName] = arguments[i];
}
return wmiParameters;
}
}
private class InstanceHandler : BaseHandler, IWmiObject
{
private readonly ManagementObject wmiObject;
public InstanceHandler(ManagementObject wmiObject) => this.wmiObject = wmiObject;
public override object? Invoke(object proxy, MethodInfo method, object[] arguments)
{
if (method.DeclaringType == typeof(IWmiObject))
{
return method.Invoke(this, arguments);
}
// TODO: proper property support
if (method.Name.StartsWith("set_"))
{
this.wmiObject[method.Name.Substring(4)] = arguments[0];
return null;
}
if (method.Name.StartsWith("get_"))
{
return this.wmiObject[method.Name.Substring(4)];
}
string methodName = method.Name;
using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null :
this.GetMethodParameters(this.wmiObject, methodName, method.GetParameters(), arguments);
using ManagementBaseObject result = this.wmiObject.InvokeMethod(methodName, wmiParameters, null);
this.CheckError(result);
return null;
}
}
private class ClassHandler : BaseHandler
{
private readonly ManagementClass wmiClass;
private readonly string className;
public ClassHandler(ManagementScope wmiScope, string className)
{
this.wmiClass = new ManagementClass(wmiScope, new ManagementPath(className), null);
this.className = className;
}
public override object? Invoke(object proxy, MethodInfo method, object[] arguments)
{
ParameterInfo[] methodParameters = method.GetParameters();
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)
{
return ProxyFactory.Create(new InstanceHandler(wmiObject), method.ReturnType, true);
}
return null;
}
string methodName = method.Name;
using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null :
this.GetMethodParameters(this.wmiClass, methodName, methodParameters, arguments);
using ManagementBaseObject result = this.wmiClass.InvokeMethod(methodName, wmiParameters, null);
this.CheckError(result);
return null;
}
}
/// <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
{
WmiClassName className = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0];
return (T)ProxyFactory.Create(new ClassHandler(this.wmiScope, className.Name), typeof(T), true);
}
}
}

144
src/Core/WinSWCore/WmiSchema.cs Executable file → Normal file
View File

@ -1,72 +1,72 @@
namespace WMI
{
public enum ServiceType
{
KernalDriver = 1,
FileSystemDriver = 2,
Adapter = 4,
RecognizerDriver = 8,
OwnProcess = 16,
ShareProcess = 32,
InteractiveProcess = 256,
}
public enum ErrorControl
{
UserNotNotified = 0,
UserNotified = 1,
SystemRestartedWithLastKnownGoodConfiguration = 2,
SystemAttemptsToStartWithAGoodConfiguration = 3
}
public enum StartMode
{
/// <summary>
/// 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>
Disabled,
}
[WmiClassName("Win32_Service")]
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);
IWin32Service? Select(string name);
}
// https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service
public interface IWin32Service : IWmiObject
{
bool Started { get; }
void Delete();
void StartService();
void StopService();
}
}
namespace WMI
{
public enum ServiceType
{
KernalDriver = 1,
FileSystemDriver = 2,
Adapter = 4,
RecognizerDriver = 8,
OwnProcess = 16,
ShareProcess = 32,
InteractiveProcess = 256,
}
public enum ErrorControl
{
UserNotNotified = 0,
UserNotified = 1,
SystemRestartedWithLastKnownGoodConfiguration = 2,
SystemAttemptsToStartWithAGoodConfiguration = 3
}
public enum StartMode
{
/// <summary>
/// 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>
Disabled,
}
[WmiClassName("Win32_Service")]
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);
IWin32Service? Select(string name);
}
// https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service
public interface IWin32Service : IWmiObject
{
bool Started { get; }
void Delete();
void StartService();
void StopService();
}
}

View File

@ -5,6 +5,7 @@ using System.IO;
using System.Text;
using System.Xml;
using log4net;
using WinSW.Configuration;
using WinSW.Extensions;
using WinSW.Util;
using static WinSW.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods;
@ -179,7 +180,7 @@ namespace WinSW.Plugins.RunawayProcessKiller
return parameters.Environment;
}
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
public override void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
// We expect the upper logic to process any errors
// TODO: a better parser API for types would be useful

View File

@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.Xml;
using log4net;
using WinSW.Configuration;
using WinSW.Extensions;
using WinSW.Util;
namespace WinSW.Plugins.SharedDirectoryMapper
{
public class SharedDirectoryMapper : AbstractWinSWExtension
@ -25,7 +27,7 @@ namespace WinSW.Plugins.SharedDirectoryMapper
this._entries.Add(config);
}
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
public override void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
XmlNodeList? mapNodes = XmlHelper.SingleNode(node, "mapping", false)!.SelectNodes("map");
if (mapNodes != null)

View File

@ -1,445 +1,445 @@
using System;
using System.Diagnostics;
using NUnit.Framework;
using WinSW;
using winswTests.Util;
using WMI;
namespace winswTests
{
[TestFixture]
public class ServiceDescriptorTests
{
private ServiceDescriptor _extendedServiceDescriptor;
private const string ExpectedWorkingDirectory = @"Z:\Path\SubPath";
private const string Username = "User";
private const string Password = "Password";
private const string Domain = "Domain";
private const string AllowServiceAccountLogonRight = "true";
[SetUp]
public void SetUp()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
}
[Test]
public void DefaultStartMode()
{
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
}
[Test]
public void IncorrectStartMode()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<startmode>roll</startmode>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(() => this._extendedServiceDescriptor.StartMode, Throws.ArgumentException);
}
[Test]
public void ChangedStartMode()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<startmode>manual</startmode>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
}
[Test]
public void VerifyWorkingDirectory()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory));
}
[Test]
public void VerifyServiceLogonRight()
{
Assert.That(this._extendedServiceDescriptor.AllowServiceAcountLogonRight, Is.True);
}
[Test]
public void VerifyUsername()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.ServiceAccountUser, Is.EqualTo(Domain + "\\" + Username));
}
[Test]
public void VerifyPassword()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.ServiceAccountPassword, Is.EqualTo(Password));
}
[Test]
public void Priority()
{
var sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>normal</priority></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Normal));
sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>idle</priority></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Idle));
sd = ServiceDescriptor.FromXML("<service><id>test</id></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Normal));
}
[Test]
public void StopParentProcessFirstIsFalseByDefault()
{
Assert.That(this._extendedServiceDescriptor.StopParentProcessFirst, Is.False);
}
[Test]
public void CanParseStopParentProcessFirst()
{
const string seedXml = "<service>"
+ "<stopparentprocessfirst>true</stopparentprocessfirst>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopParentProcessFirst, Is.True);
}
[Test]
public void CanParseStopTimeout()
{
const string seedXml = "<service>"
+ "<stoptimeout>60sec</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromSeconds(60)));
}
[Test]
public void CanParseStopTimeoutFromMinutes()
{
const string seedXml = "<service>"
+ "<stoptimeout>10min</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromMinutes(10)));
}
[Test]
public void CanParseLogname()
{
const string seedXml = "<service>"
+ "<logname>MyTestApp</logname>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.LogName, Is.EqualTo("MyTestApp"));
}
[Test]
public void CanParseOutfileDisabled()
{
const string seedXml = "<service>"
+ "<outfiledisabled>true</outfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.OutFileDisabled, Is.True);
}
[Test]
public void CanParseErrfileDisabled()
{
const string seedXml = "<service>"
+ "<errfiledisabled>true</errfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.ErrFileDisabled, Is.True);
}
[Test]
public void CanParseOutfilePattern()
{
const string seedXml = "<service>"
+ "<outfilepattern>.out.test.log</outfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.OutFilePattern, Is.EqualTo(".out.test.log"));
}
[Test]
public void CanParseErrfilePattern()
{
const string seedXml = "<service>"
+ "<errfilepattern>.err.test.log</errfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.ErrFilePattern, Is.EqualTo(".err.test.log"));
}
[Test]
public void LogModeRollBySize()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-size\">"
+ "<sizeThreshold>112</sizeThreshold>"
+ "<keepFiles>113</keepFiles>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as SizeBasedRollingLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.SizeTheshold, Is.EqualTo(112 * 1024));
Assert.That(logHandler.FilesToKeep, Is.EqualTo(113));
}
[Test]
public void LogModeRollByTime()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-time\">"
+ "<period>7</period>"
+ "<pattern>log pattern</pattern>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as TimeBasedRollingLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.Period, Is.EqualTo(7));
Assert.That(logHandler.Pattern, Is.EqualTo("log pattern"));
}
[Test]
public void LogModeRollBySizeTime()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-size-time\">"
+ "<sizeThreshold>10240</sizeThreshold>"
+ "<pattern>yyyy-MM-dd</pattern>"
+ "<autoRollAtTime>00:00:00</autoRollAtTime>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as RollingSizeTimeLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.SizeTheshold, Is.EqualTo(10240 * 1024));
Assert.That(logHandler.FilePattern, Is.EqualTo("yyyy-MM-dd"));
Assert.That(logHandler.AutoRollAtTime, Is.EqualTo((TimeSpan?)new TimeSpan(0, 0, 0)));
}
[Test]
public void VerifyServiceLogonRightGraceful()
{
const string seedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "<allowservicelogon>true1</allowservicelogon>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.False);
}
[Test]
public void VerifyServiceLogonRightOmitted()
{
const string seedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.False);
}
[Test]
public void VerifyWaitHint_FullXML()
{
var sd = ConfigXmlBuilder.create()
.WithTag("waithint", "20 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(20)));
}
/// <summary>
/// Test for https://github.com/kohsuke/winsw/issues/159
/// </summary>
[Test]
public void VerifyWaitHint_XMLWithoutVersion()
{
var sd = ConfigXmlBuilder.create(printXMLVersion: false)
.WithTag("waithint", "21 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(21)));
}
[Test]
public void VerifyWaitHint_XMLWithoutComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null)
.WithTag("waithint", "22 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(22)));
}
[Test]
public void VerifyWaitHint_XMLWithoutVersionAndComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null, printXMLVersion: false)
.WithTag("waithint", "23 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(23)));
}
[Test]
public void VerifySleepTime()
{
var sd = ConfigXmlBuilder.create().WithTag("sleeptime", "3 hrs").ToServiceDescriptor(true);
Assert.That(sd.SleepTime, Is.EqualTo(TimeSpan.FromHours(3)));
}
[Test]
public void VerifyResetFailureAfter()
{
var sd = ConfigXmlBuilder.create().WithTag("resetfailure", "75 sec").ToServiceDescriptor(true);
Assert.That(sd.ResetFailureAfter, Is.EqualTo(TimeSpan.FromSeconds(75)));
}
[Test]
public void VerifyStopTimeout()
{
var sd = ConfigXmlBuilder.create().WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true);
Assert.That(sd.StopTimeout, Is.EqualTo(TimeSpan.FromSeconds(35)));
}
/// <summary>
/// https://github.com/kohsuke/winsw/issues/178
/// </summary>
[Test]
public void Arguments_LegacyParam()
{
var sd = ConfigXmlBuilder.create().WithTag("arguments", "arg").ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo("arg"));
}
[Test]
public void Arguments_NewParam_Single()
{
var sd = ConfigXmlBuilder.create()
.WithTag("argument", "--arg1=2")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg1=2"));
}
[Test]
public void Arguments_NewParam_MultipleArgs()
{
var sd = ConfigXmlBuilder.create()
.WithTag("argument", "--arg1=2")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg1=2 --arg2=123 --arg3=null"));
}
/// <summary>
/// Ensures that the new single-argument field has a higher priority.
/// </summary>
[Test]
public void Arguments_Bothparam_Priorities()
{
var sd = ConfigXmlBuilder.create()
.WithTag("arguments", "--arg1=2 --arg2=3")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg2=123 --arg3=null"));
}
[TestCase(true)]
[TestCase(false)]
public void DelayedStart_RoundTrip(bool enabled)
{
var bldr = ConfigXmlBuilder.create();
if (enabled)
{
bldr = bldr.WithDelayedAutoStart();
}
var sd = bldr.ToServiceDescriptor();
Assert.That(sd.DelayedAutoStart, Is.EqualTo(enabled));
}
}
}
using System;
using System.Diagnostics;
using NUnit.Framework;
using WinSW;
using winswTests.Util;
using WMI;
namespace winswTests
{
[TestFixture]
public class ServiceDescriptorTests
{
private ServiceDescriptor _extendedServiceDescriptor;
private const string ExpectedWorkingDirectory = @"Z:\Path\SubPath";
private const string Username = "User";
private const string Password = "Password";
private const string Domain = "Domain";
private const string AllowServiceAccountLogonRight = "true";
[SetUp]
public void SetUp()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
}
[Test]
public void DefaultStartMode()
{
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
}
[Test]
public void IncorrectStartMode()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<startmode>roll</startmode>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(() => this._extendedServiceDescriptor.StartMode, Throws.ArgumentException);
}
[Test]
public void ChangedStartMode()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<startmode>manual</startmode>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
}
[Test]
public void VerifyWorkingDirectory()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory));
}
[Test]
public void VerifyServiceLogonRight()
{
Assert.That(_extendedServiceDescriptor.ServiceAccount.AllowServiceAcountLogonRight, Is.True);
}
[Test]
public void VerifyUsername()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.That(_extendedServiceDescriptor.ServiceAccount.ServiceAccountUser, Is.EqualTo(Domain + "\\" + Username));
}
[Test]
public void VerifyPassword()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.That(_extendedServiceDescriptor.ServiceAccount.ServiceAccountPassword, Is.EqualTo(Password));
}
[Test]
public void Priority()
{
var sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>normal</priority></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Normal));
sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>idle</priority></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Idle));
sd = ServiceDescriptor.FromXML("<service><id>test</id></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Normal));
}
[Test]
public void StopParentProcessFirstIsFalseByDefault()
{
Assert.That(this._extendedServiceDescriptor.StopParentProcessFirst, Is.False);
}
[Test]
public void CanParseStopParentProcessFirst()
{
const string seedXml = "<service>"
+ "<stopparentprocessfirst>true</stopparentprocessfirst>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopParentProcessFirst, Is.True);
}
[Test]
public void CanParseStopTimeout()
{
const string seedXml = "<service>"
+ "<stoptimeout>60sec</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromSeconds(60)));
}
[Test]
public void CanParseStopTimeoutFromMinutes()
{
const string seedXml = "<service>"
+ "<stoptimeout>10min</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromMinutes(10)));
}
[Test]
public void CanParseLogname()
{
const string seedXml = "<service>"
+ "<logname>MyTestApp</logname>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.LogName, Is.EqualTo("MyTestApp"));
}
[Test]
public void CanParseOutfileDisabled()
{
const string seedXml = "<service>"
+ "<outfiledisabled>true</outfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.Log.OutFileDisabled, Is.True);
}
[Test]
public void CanParseErrfileDisabled()
{
const string seedXml = "<service>"
+ "<errfiledisabled>true</errfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.Log.ErrFileDisabled, Is.True);
}
[Test]
public void CanParseOutfilePattern()
{
const string seedXml = "<service>"
+ "<outfilepattern>.out.test.log</outfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.Log.OutFilePattern, Is.EqualTo(".out.test.log"));
}
[Test]
public void CanParseErrfilePattern()
{
const string seedXml = "<service>"
+ "<errfilepattern>.err.test.log</errfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.Log.ErrFilePattern, Is.EqualTo(".err.test.log"));
}
[Test]
public void LogModeRollBySize()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-size\">"
+ "<sizeThreshold>112</sizeThreshold>"
+ "<keepFiles>113</keepFiles>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.Log.CreateLogHandler() as SizeBasedRollingLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.SizeTheshold, Is.EqualTo(112 * 1024));
Assert.That(logHandler.FilesToKeep, Is.EqualTo(113));
}
[Test]
public void LogModeRollByTime()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-time\">"
+ "<period>7</period>"
+ "<pattern>log pattern</pattern>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.Log.CreateLogHandler() as TimeBasedRollingLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.Period, Is.EqualTo(7));
Assert.That(logHandler.Pattern, Is.EqualTo("log pattern"));
}
[Test]
public void LogModeRollBySizeTime()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-size-time\">"
+ "<sizeThreshold>10240</sizeThreshold>"
+ "<pattern>yyyy-MM-dd</pattern>"
+ "<autoRollAtTime>00:00:00</autoRollAtTime>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.Log.CreateLogHandler() as RollingSizeTimeLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.SizeTheshold, Is.EqualTo(10240 * 1024));
Assert.That(logHandler.FilePattern, Is.EqualTo("yyyy-MM-dd"));
Assert.That(logHandler.AutoRollAtTime, Is.EqualTo((TimeSpan?)new TimeSpan(0, 0, 0)));
}
[Test]
public void VerifyServiceLogonRightGraceful()
{
const string seedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "<allowservicelogon>true1</allowservicelogon>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.ServiceAccount.AllowServiceAcountLogonRight, Is.False);
}
[Test]
public void VerifyServiceLogonRightOmitted()
{
const string seedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.ServiceAccount.AllowServiceAcountLogonRight, Is.False);
}
[Test]
public void VerifyWaitHint_FullXML()
{
var sd = ConfigXmlBuilder.create()
.WithTag("waithint", "20 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(20)));
}
/// <summary>
/// Test for https://github.com/kohsuke/winsw/issues/159
/// </summary>
[Test]
public void VerifyWaitHint_XMLWithoutVersion()
{
var sd = ConfigXmlBuilder.create(printXMLVersion: false)
.WithTag("waithint", "21 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(21)));
}
[Test]
public void VerifyWaitHint_XMLWithoutComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null)
.WithTag("waithint", "22 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(22)));
}
[Test]
public void VerifyWaitHint_XMLWithoutVersionAndComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null, printXMLVersion: false)
.WithTag("waithint", "23 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(23)));
}
[Test]
public void VerifySleepTime()
{
var sd = ConfigXmlBuilder.create().WithTag("sleeptime", "3 hrs").ToServiceDescriptor(true);
Assert.That(sd.SleepTime, Is.EqualTo(TimeSpan.FromHours(3)));
}
[Test]
public void VerifyResetFailureAfter()
{
var sd = ConfigXmlBuilder.create().WithTag("resetfailure", "75 sec").ToServiceDescriptor(true);
Assert.That(sd.ResetFailureAfter, Is.EqualTo(TimeSpan.FromSeconds(75)));
}
[Test]
public void VerifyStopTimeout()
{
var sd = ConfigXmlBuilder.create().WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true);
Assert.That(sd.StopTimeout, Is.EqualTo(TimeSpan.FromSeconds(35)));
}
/// <summary>
/// https://github.com/kohsuke/winsw/issues/178
/// </summary>
[Test]
public void Arguments_LegacyParam()
{
var sd = ConfigXmlBuilder.create().WithTag("arguments", "arg").ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo("arg"));
}
[Test]
public void Arguments_NewParam_Single()
{
var sd = ConfigXmlBuilder.create()
.WithTag("argument", "--arg1=2")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg1=2"));
}
[Test]
public void Arguments_NewParam_MultipleArgs()
{
var sd = ConfigXmlBuilder.create()
.WithTag("argument", "--arg1=2")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg1=2 --arg2=123 --arg3=null"));
}
/// <summary>
/// Ensures that the new single-argument field has a higher priority.
/// </summary>
[Test]
public void Arguments_Bothparam_Priorities()
{
var sd = ConfigXmlBuilder.create()
.WithTag("arguments", "--arg1=2 --arg2=3")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg2=123 --arg3=null"));
}
[TestCase(true)]
[TestCase(false)]
public void DelayedStart_RoundTrip(bool enabled)
{
var bldr = ConfigXmlBuilder.create();
if (enabled)
{
bldr = bldr.WithDelayedAutoStart();
}
var sd = bldr.ToServiceDescriptor();
Assert.That(sd.DelayedAutoStart, Is.EqualTo(enabled));
}
}
}

View File

@ -0,0 +1,149 @@
using System;
using NUnit.Framework;
using WinSW;
using WinSW.Configuration;
using WinSW.Native;
namespace winswTests
{
class ServiceDescriptorYamlTest
{
private readonly string MinimalYaml = @"id: myapp
name: This is a test
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
description: This is test winsw";
private readonly DefaultWinSWSettings Defaults = new DefaultWinSWSettings();
[Test]
public void Parse_must_implemented_value_test()
{
var yml = @"name: This is a test
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
description: This is test winsw";
Assert.That(() =>
{
_ = ServiceDescriptorYaml.FromYaml(yml).Configurations.Id;
}, Throws.TypeOf<InvalidOperationException>());
}
[Test]
public void Default_value_map_test()
{
var configs = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations;
Assert.IsNotNull(configs.ExecutablePath);
Assert.IsNotNull(configs.BaseName);
Assert.IsNotNull(configs.BasePath);
}
[Test]
public void Parse_downloads()
{
var yml = @"download:
-
from: www.sample.com
to: c://tmp
-
from: www.sample2.com
to: d://tmp
-
from: www.sample3.com
to: d://temp";
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
Assert.AreEqual(3, configs.Downloads.Count);
Assert.AreEqual("www.sample.com", configs.Downloads[0].From);
Assert.AreEqual("c://tmp", configs.Downloads[0].To);
}
[Test]
public void Parse_serviceaccount()
{
var yml = @"id: myapp
name: winsw
description: yaml test
executable: java
serviceaccount:
user: testuser
domain: mydomain
password: pa55w0rd
allowservicelogon: yes";
var serviceAccount = ServiceDescriptorYaml.FromYaml(yml).Configurations.ServiceAccount;
Assert.AreEqual("mydomain\\testuser", serviceAccount.ServiceAccountUser);
Assert.AreEqual(true, serviceAccount.AllowServiceAcountLogonRight);
Assert.AreEqual("pa55w0rd", serviceAccount.ServiceAccountPassword);
Assert.AreEqual(true, serviceAccount.HasServiceAccount());
}
[Test]
public void Parse_environment_variables()
{
var yml = @"id: myapp
name: WinSW
executable: java
description: env test
env:
-
name: MY_TOOL_HOME
value: 'C:\etc\tools\myTool'
-
name: LM_LICENSE_FILE
value: host1;host2";
var envs = ServiceDescriptorYaml.FromYaml(yml).Configurations.EnvironmentVariables;
Assert.That(@"C:\etc\tools\myTool", Is.EqualTo(envs["MY_TOOL_HOME"]));
Assert.That("host1;host2", Is.EqualTo(envs["LM_LICENSE_FILE"]));
}
[Test]
public void Parse_log()
{
var yml = @"id: myapp
name: winsw
description: yaml test
executable: java
log:
mode: roll
logpath: 'D://winsw/logs'";
var config = ServiceDescriptorYaml.FromYaml(yml).Configurations;
Assert.AreEqual("roll", config.LogMode);
Assert.AreEqual("D://winsw/logs", config.LogDirectory);
}
[Test]
public void Parse_onfailure_actions()
{
var yml = @"id: myapp
name: winsw
description: yaml test
executable: java
onFailure:
-
action: restart
delay: 5 sec
-
action: reboot
delay: 10 min";
var onFailure = ServiceDescriptorYaml.FromYaml(yml).Configurations.FailureActions;
Assert.That(onFailure[0].Type, Is.EqualTo(SC_ACTION_TYPE.SC_ACTION_RESTART));
Assert.That(onFailure[1].Type, Is.EqualTo(SC_ACTION_TYPE.SC_ACTION_REBOOT));
Assert.That(TimeSpan.FromMilliseconds(onFailure[0].Delay), Is.EqualTo(TimeSpan.FromSeconds(5)));
Assert.That(TimeSpan.FromMilliseconds(onFailure[1].Delay), Is.EqualTo(TimeSpan.FromMinutes(10)));
}
}
}

View File

@ -59,6 +59,10 @@ namespace winswTests.Util
properties.Remove("Caption");
properties.Remove("Description");
properties.Remove("Executable");
properties.Remove("BaseName");
properties.Remove("BasePath");
properties.Remove("Log");
properties.Remove("ServiceAccount");
return properties;
}
}

View File

@ -1,87 +1,87 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{077C2CEC-B687-4B53-86E9-C1A1BF5554E5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{BC4AD891-E87E-4F30-867C-FD8084A29E5D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5297623A-1A95-4F89-9AAE-DA634081EC86}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunawayProcessKiller", "Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj", "{57284B7A-82A4-407A-B706-EBEA6BF8EA13}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AA414F46-B863-473A-A0E0-C2971B3396AE}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
..\examples\sample-allOptions.xml = ..\examples\sample-allOptions.xml
..\examples\sample-minimal.xml = ..\examples\sample-minimal.xml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Win32 = Debug|Win32
Release|Any CPU = Release|Any CPU
Release|Win32 = Release|Win32
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.ActiveCfg = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.Build.0 = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.Build.0 = Release|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.ActiveCfg = Release|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.Build.0 = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.ActiveCfg = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.Build.0 = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.Build.0 = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.ActiveCfg = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.Build.0 = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Win32.Build.0 = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Any CPU.Build.0 = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Win32.ActiveCfg = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Win32.Build.0 = Release|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Win32.ActiveCfg = Debug|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Win32.Build.0 = Debug|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Any CPU.Build.0 = Release|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Win32.ActiveCfg = Release|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Win32.Build.0 = Release|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Win32.ActiveCfg = Debug|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Win32.Build.0 = Debug|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Any CPU.Build.0 = Release|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Win32.ActiveCfg = Release|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Win32.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0DE77F55-ADE5-43C1-999A-0BC81153B039} = {5297623A-1A95-4F89-9AAE-DA634081EC86}
{93843402-842B-44B4-B303-AEE829BE0B43} = {077C2CEC-B687-4B53-86E9-C1A1BF5554E5}
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D}
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06} = {5297623A-1A95-4F89-9AAE-DA634081EC86}
{57284B7A-82A4-407A-B706-EBEA6BF8EA13} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D}
EndGlobalSection
EndGlobal
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{077C2CEC-B687-4B53-86E9-C1A1BF5554E5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{BC4AD891-E87E-4F30-867C-FD8084A29E5D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5297623A-1A95-4F89-9AAE-DA634081EC86}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunawayProcessKiller", "Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj", "{57284B7A-82A4-407A-B706-EBEA6BF8EA13}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AA414F46-B863-473A-A0E0-C2971B3396AE}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
..\examples\sample-allOptions.xml = ..\examples\sample-allOptions.xml
..\examples\sample-minimal.xml = ..\examples\sample-minimal.xml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Win32 = Debug|Win32
Release|Any CPU = Release|Any CPU
Release|Win32 = Release|Win32
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.ActiveCfg = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.Build.0 = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.Build.0 = Release|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.ActiveCfg = Release|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.Build.0 = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.ActiveCfg = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.Build.0 = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.Build.0 = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.ActiveCfg = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.Build.0 = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Win32.Build.0 = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Any CPU.Build.0 = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Win32.ActiveCfg = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Win32.Build.0 = Release|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Win32.ActiveCfg = Debug|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Win32.Build.0 = Debug|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Any CPU.Build.0 = Release|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Win32.ActiveCfg = Release|Any CPU
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Win32.Build.0 = Release|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Win32.ActiveCfg = Debug|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Win32.Build.0 = Debug|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Any CPU.Build.0 = Release|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Win32.ActiveCfg = Release|Any CPU
{57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Win32.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0DE77F55-ADE5-43C1-999A-0BC81153B039} = {5297623A-1A95-4F89-9AAE-DA634081EC86}
{93843402-842B-44B4-B303-AEE829BE0B43} = {077C2CEC-B687-4B53-86E9-C1A1BF5554E5}
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D}
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06} = {5297623A-1A95-4F89-9AAE-DA634081EC86}
{57284B7A-82A4-407A-B706-EBEA6BF8EA13} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D}
EndGlobalSection
EndGlobal