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

View File

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

View File

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

View File

@ -12,6 +12,8 @@ namespace WinSW.Configuration
/// </summary> /// </summary>
public sealed class DefaultWinSWSettings : IWinSWConfiguration 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 Id => throw new InvalidOperationException(nameof(this.Id) + " must be specified.");
public string Caption => throw new InvalidOperationException(nameof(this.Caption) + " 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; public string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName;
// Installation // Installation
public bool AllowServiceAcountLogonRight => false;
public string? ServiceAccountPassword => null;
public string? ServiceAccountUser => null;
public Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0]; public Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0];
public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1); public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1);
@ -66,17 +62,67 @@ namespace WinSW.Configuration
public bool Interactive => false; public bool Interactive => false;
// Logging // 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 // Environment
public List<Download> Downloads => new List<Download>(0); public List<Download> Downloads => new List<Download>(0);
@ -88,5 +134,32 @@ namespace WinSW.Configuration
// Extensions // Extensions
public XmlNode? ExtensionsConfiguration => null; 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; } bool HideWindow { get; }
// Installation // Installation
bool AllowServiceAcountLogonRight { get; }
string? ServiceAccountPassword { get; }
string? ServiceAccountUser { get; }
Native.SC_ACTION[] FailureActions { get; } Native.SC_ACTION[] FailureActions { get; }
TimeSpan ResetFailureAfter { get; } TimeSpan ResetFailureAfter { get; }
@ -60,12 +54,17 @@ namespace WinSW.Configuration
bool Interactive { get; } bool Interactive { get; }
// Logging /// <summary>
/// Destination for logging.
/// If undefined, a default one should be used.
/// </summary>
string LogDirectory { get; } string LogDirectory { get; }
// TODO: replace by enum // TODO: replace by enum
string LogMode { get; } string LogMode { get; }
Log Log { get; }
// Environment // Environment
List<Download> Downloads { get; } List<Download> Downloads { get; }
@ -76,5 +75,18 @@ namespace WinSW.Configuration
// Extensions // Extensions
XmlNode? ExtensionsConfiguration { get; } 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;
using System.IO; using System.IO;
using System.Net; using System.Net;
#if !VNEXT #if !VNEXT
using System.Reflection; using System.Reflection;
#endif #endif
using System.Text; using System.Text;
#if VNEXT #if VNEXT
using System.Threading.Tasks; using System.Threading.Tasks;
#endif #endif
using System.Xml; using System.Xml;
using log4net; using log4net;
using WinSW.Util; using WinSW.Util;
namespace WinSW namespace WinSW
{ {
/// <summary> /// <summary>
/// Specify the download activities prior to the launch. /// Specify the download activities prior to the launch.
/// This enables self-updating services. /// This enables self-updating services.
/// </summary> /// </summary>
public class Download public class Download
{ {
public enum AuthType public enum AuthType
{ {
None = 0, None = 0,
Sspi, Sspi,
Basic Basic
} }
private static readonly ILog Logger = LogManager.GetLogger(typeof(Download)); private static readonly ILog Logger = LogManager.GetLogger(typeof(Download));
public readonly string From; public readonly string From;
public readonly string To; public readonly string To;
public readonly AuthType Auth; public readonly AuthType Auth;
public readonly string? Username; public readonly string? Username;
public readonly string? Password; public readonly string? Password;
public readonly bool UnsecureAuth; public readonly bool UnsecureAuth;
public readonly bool FailOnError; public readonly bool FailOnError;
public readonly string? Proxy; public readonly string? Proxy;
public string ShortId => $"(download from {this.From})"; public string ShortId => $"(download from {this.From})";
static Download() static Download()
{ {
#if NET461 #if NET461
// If your app runs on .NET Framework 4.7 or later versions, but targets an earlier version // If your app runs on .NET Framework 4.7 or later versions, but targets an earlier version
AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", false); AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", false);
#elif !VNEXT #elif !VNEXT
// If your app runs on .NET Framework 4.6, but targets an earlier version // 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 }); 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 Tls12 = (SecurityProtocolType)0x00000C00;
const SecurityProtocolType Tls11 = (SecurityProtocolType)0x00000300; const SecurityProtocolType Tls11 = (SecurityProtocolType)0x00000300;
// Windows 7 and Windows Server 2008 R2 // Windows 7 and Windows Server 2008 R2
if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1) if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1)
{ {
try try
{ {
ServicePointManager.SecurityProtocol |= Tls11 | Tls12; ServicePointManager.SecurityProtocol |= Tls11 | Tls12;
Logger.Info("TLS 1.1/1.2 enabled"); Logger.Info("TLS 1.1/1.2 enabled");
} }
catch (NotSupportedException) catch (NotSupportedException)
{ {
Logger.Info("TLS 1.1/1.2 disabled"); Logger.Info("TLS 1.1/1.2 disabled");
} }
} }
#endif #endif
} }
// internal // internal
public Download( public Download(
string from, string from,
string to, string to,
bool failOnError = false, bool failOnError = false,
AuthType auth = AuthType.None, AuthType auth = AuthType.None,
string? username = null, string? username = null,
string? password = null, string? password = null,
bool unsecureAuth = false, bool unsecureAuth = false,
string? proxy = null) string? proxy = null)
{ {
this.From = from; this.From = from;
this.To = to; this.To = to;
this.FailOnError = failOnError; this.FailOnError = failOnError;
this.Proxy = proxy; this.Proxy = proxy;
this.Auth = auth; this.Auth = auth;
this.Username = username; this.Username = username;
this.Password = password; this.Password = password;
this.UnsecureAuth = unsecureAuth; this.UnsecureAuth = unsecureAuth;
} }
/// <summary> /// <summary>
/// Constructs the download setting sfrom the XML entry /// Constructs the download setting sfrom the XML entry
/// </summary> /// </summary>
/// <param name="n">XML element</param> /// <param name="n">XML element</param>
/// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid.</exception> /// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid.</exception>
internal Download(XmlElement n) internal Download(XmlElement n)
{ {
this.From = XmlHelper.SingleAttribute<string>(n, "from"); this.From = XmlHelper.SingleAttribute<string>(n, "from");
this.To = XmlHelper.SingleAttribute<string>(n, "to"); this.To = XmlHelper.SingleAttribute<string>(n, "to");
// All arguments below are optional // All arguments below are optional
this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false); this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false);
this.Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null); this.Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null);
this.Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.None); this.Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.None);
this.Username = XmlHelper.SingleAttribute<string>(n, "user", null); this.Username = XmlHelper.SingleAttribute<string>(n, "user", null);
this.Password = XmlHelper.SingleAttribute<string>(n, "password", null); this.Password = XmlHelper.SingleAttribute<string>(n, "password", null);
this.UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false); this.UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false);
if (this.Auth == AuthType.Basic) if (this.Auth == AuthType.Basic)
{ {
// Allow it only for HTTPS or for UnsecureAuth // Allow it only for HTTPS or for UnsecureAuth
if (!this.From.StartsWith("https:") && !this.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 + 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"); "If you really want this you must enable 'unsecureAuth' in the configuration");
} }
// Also fail if there is no user/password // Also fail if there is no user/password
if (this.Username is null) if (this.Username is null)
{ {
throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId); throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId);
} }
if (this.Password is null) if (this.Password is null)
{ {
throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId); 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 // Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest
private void SetBasicAuthHeader(WebRequest request, string username, string password) private void SetBasicAuthHeader(WebRequest request, string username, string password)
{ {
string authInfo = username + ":" + password; string authInfo = username + ":" + password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo)); authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo; request.Headers["Authorization"] = "Basic " + authInfo;
} }
/// <summary> /// <summary>
/// Downloads the requested file and puts it to the specified target. /// Downloads the requested file and puts it to the specified target.
/// </summary> /// </summary>
/// <exception cref="WebException"> /// <exception cref="WebException">
/// Download failure. FailOnError flag should be processed outside. /// Download failure. FailOnError flag should be processed outside.
/// </exception> /// </exception>
#if VNEXT #if VNEXT
public async Task PerformAsync() public async Task PerformAsync()
#else #else
public void Perform() public void Perform()
#endif #endif
{ {
WebRequest request = WebRequest.Create(this.From); WebRequest request = WebRequest.Create(this.From);
if (!string.IsNullOrEmpty(this.Proxy)) if (!string.IsNullOrEmpty(this.Proxy))
{ {
CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!); CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!);
if (proxyInformation.Credentials != null) if (proxyInformation.Credentials != null)
{ {
request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials); request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials);
} }
else else
{ {
request.Proxy = new WebProxy(proxyInformation.ServerAddress); request.Proxy = new WebProxy(proxyInformation.ServerAddress);
} }
} }
switch (this.Auth) switch (this.Auth)
{ {
case AuthType.None: case AuthType.None:
// Do nothing // Do nothing
break; break;
case AuthType.Sspi: case AuthType.Sspi:
request.UseDefaultCredentials = true; request.UseDefaultCredentials = true;
request.PreAuthenticate = true; request.PreAuthenticate = true;
request.Credentials = CredentialCache.DefaultCredentials; request.Credentials = CredentialCache.DefaultCredentials;
break; break;
case AuthType.Basic: case AuthType.Basic:
this.SetBasicAuthHeader(request, this.Username!, this.Password!); this.SetBasicAuthHeader(request, this.Username!, this.Password!);
break; break;
default: default:
throw new WebException("Code defect. Unsupported authentication type: " + this.Auth); throw new WebException("Code defect. Unsupported authentication type: " + this.Auth);
} }
bool supportsIfModifiedSince = false; bool supportsIfModifiedSince = false;
if (request is HttpWebRequest httpRequest && File.Exists(this.To)) if (request is HttpWebRequest httpRequest && File.Exists(this.To))
{ {
supportsIfModifiedSince = true; supportsIfModifiedSince = true;
httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To); httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To);
} }
DateTime lastModified = default; DateTime lastModified = default;
string tmpFilePath = this.To + ".tmp"; string tmpFilePath = this.To + ".tmp";
try try
{ {
#if VNEXT #if VNEXT
using (WebResponse response = await request.GetResponseAsync()) using (WebResponse response = await request.GetResponseAsync())
#else #else
using (WebResponse response = request.GetResponse()) using (WebResponse response = request.GetResponse())
#endif #endif
using (Stream responseStream = response.GetResponseStream()) using (Stream responseStream = response.GetResponseStream())
using (FileStream tmpStream = new FileStream(tmpFilePath, FileMode.Create)) using (FileStream tmpStream = new FileStream(tmpFilePath, FileMode.Create))
{ {
if (supportsIfModifiedSince) if (supportsIfModifiedSince)
{ {
lastModified = ((HttpWebResponse)response).LastModified; lastModified = ((HttpWebResponse)response).LastModified;
} }
#if VNEXT #if VNEXT
await responseStream.CopyToAsync(tmpStream); await responseStream.CopyToAsync(tmpStream);
#elif NET20 #elif NET20
CopyStream(responseStream, tmpStream); CopyStream(responseStream, tmpStream);
#else #else
responseStream.CopyTo(tmpStream); responseStream.CopyTo(tmpStream);
#endif #endif
} }
FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To); FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To);
if (supportsIfModifiedSince) if (supportsIfModifiedSince)
{ {
File.SetLastWriteTime(this.To, lastModified); File.SetLastWriteTime(this.To, lastModified);
} }
} }
catch (WebException e) catch (WebException e)
{ {
if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified) if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified)
{ {
Logger.Info($"Skipped downloading unmodified resource '{this.From}'"); Logger.Info($"Skipped downloading unmodified resource '{this.From}'");
} }
else else
{ {
throw; throw;
} }
} }
} }
#if NET20 #if NET20
private static void CopyStream(Stream source, Stream destination) private static void CopyStream(Stream source, Stream destination)
{ {
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int read; int read;
while ((read = source.Read(buffer, 0, buffer.Length)) != 0) while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
{ {
destination.Write(buffer, 0, read); destination.Write(buffer, 0, read);
} }
} }
#endif #endif
} }
public class CustomProxyInformation public class CustomProxyInformation
{ {
public string ServerAddress { get; set; } public string ServerAddress { get; set; }
public NetworkCredential? Credentials { get; set; } public NetworkCredential? Credentials { get; set; }
public CustomProxyInformation(string proxy) public CustomProxyInformation(string proxy)
{ {
if (proxy.Contains("@")) if (proxy.Contains("@"))
{ {
// Extract proxy credentials // Extract proxy credentials
int credsFrom = proxy.IndexOf("://") + 3; int credsFrom = proxy.IndexOf("://") + 3;
int credsTo = proxy.LastIndexOf("@"); int credsTo = proxy.LastIndexOf("@");
string completeCredsStr = proxy.Substring(credsFrom, credsTo - credsFrom); string completeCredsStr = proxy.Substring(credsFrom, credsTo - credsFrom);
int credsSeparator = completeCredsStr.IndexOf(":"); int credsSeparator = completeCredsStr.IndexOf(":");
string username = completeCredsStr.Substring(0, credsSeparator); string username = completeCredsStr.Substring(0, credsSeparator);
string password = completeCredsStr.Substring(credsSeparator + 1); string password = completeCredsStr.Substring(credsSeparator + 1);
this.Credentials = new NetworkCredential(username, password); this.Credentials = new NetworkCredential(username, password);
this.ServerAddress = proxy.Replace(completeCredsStr + "@", string.Empty); this.ServerAddress = proxy.Replace(completeCredsStr + "@", string.Empty);
} }
else else
{ {
this.ServerAddress = proxy; this.ServerAddress = proxy;
} }
} }
} }
} }

View File

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

View File

@ -1,4 +1,5 @@
using System.Xml; using System.Xml;
using WinSW.Configuration;
namespace WinSW.Extensions namespace WinSW.Extensions
{ {
@ -10,7 +11,7 @@ namespace WinSW.Extensions
public WinSWExtensionDescriptor Descriptor { get; set; } public WinSWExtensionDescriptor Descriptor { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. #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 // Do nothing
} }

View File

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

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml; using System.Xml;
using log4net; using log4net;
using WinSW.Configuration;
namespace WinSW.Extensions namespace WinSW.Extensions
{ {
@ -9,11 +10,11 @@ namespace WinSW.Extensions
{ {
public Dictionary<string, IWinSWExtension> Extensions { get; private set; } 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)); private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager));
public WinSWExtensionManager(ServiceDescriptor serviceDescriptor) public WinSWExtensionManager(IWinSWConfiguration serviceDescriptor)
{ {
this.ServiceDescriptor = serviceDescriptor; this.ServiceDescriptor = serviceDescriptor;
this.Extensions = new Dictionary<string, IWinSWExtension>(); 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 System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
namespace WinSW.Native namespace WinSW.Native
{ {
internal static class Kernel32 internal static class Kernel32
{ {
[DllImport(Libraries.Kernel32)] [DllImport(Libraries.Kernel32)]
internal static extern bool SetStdHandle(int stdHandle, SafeFileHandle handle); internal static extern bool SetStdHandle(int stdHandle, SafeFileHandle handle);
} }
} }

View File

@ -1,108 +1,108 @@
using System; using System;
namespace WinSW namespace WinSW
{ {
// This is largely borrowed from the logback Rolling Calendar. // This is largely borrowed from the logback Rolling Calendar.
public class PeriodicRollingCalendar public class PeriodicRollingCalendar
{ {
private readonly string format; private readonly string format;
private readonly long period; private readonly long period;
private DateTime currentRoll; private DateTime currentRoll;
private DateTime nextRoll; private DateTime nextRoll;
public PeriodicRollingCalendar(string format, long period) public PeriodicRollingCalendar(string format, long period)
{ {
this.format = format; this.format = format;
this.period = period; this.period = period;
this.currentRoll = DateTime.Now; this.currentRoll = DateTime.Now;
} }
public void Init() public void Init()
{ {
this.PeriodicityType = this.DeterminePeriodicityType(); this.PeriodicityType = this.DeterminePeriodicityType();
this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period); this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period);
} }
public enum Periodicity public enum Periodicity
{ {
ERRONEOUS, ERRONEOUS,
TOP_OF_MILLISECOND, TOP_OF_MILLISECOND,
TOP_OF_SECOND, TOP_OF_SECOND,
TOP_OF_MINUTE, TOP_OF_MINUTE,
TOP_OF_HOUR, TOP_OF_HOUR,
TOP_OF_DAY TOP_OF_DAY
} }
private static readonly Periodicity[] ValidOrderedList = 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 Periodicity.TOP_OF_MILLISECOND, Periodicity.TOP_OF_SECOND, Periodicity.TOP_OF_MINUTE, Periodicity.TOP_OF_HOUR, Periodicity.TOP_OF_DAY
}; };
private Periodicity DeterminePeriodicityType() private Periodicity DeterminePeriodicityType()
{ {
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.format, this.period); PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.format, this.period);
DateTime epoch = new DateTime(1970, 1, 1); DateTime epoch = new DateTime(1970, 1, 1);
foreach (Periodicity i in ValidOrderedList) foreach (Periodicity i in ValidOrderedList)
{ {
string r0 = epoch.ToString(this.format); string r0 = epoch.ToString(this.format);
periodicRollingCalendar.PeriodicityType = i; periodicRollingCalendar.PeriodicityType = i;
DateTime next = periodicRollingCalendar.NextTriggeringTime(epoch, 1); DateTime next = periodicRollingCalendar.NextTriggeringTime(epoch, 1);
string r1 = next.ToString(this.format); string r1 = next.ToString(this.format);
if (r0 != r1) if (r0 != r1)
{ {
return i; return i;
} }
} }
return Periodicity.ERRONEOUS; return Periodicity.ERRONEOUS;
} }
private DateTime NextTriggeringTime(DateTime input, long increment) => this.PeriodicityType switch private DateTime NextTriggeringTime(DateTime input, long increment) => this.PeriodicityType switch
{ {
Periodicity.TOP_OF_MILLISECOND => Periodicity.TOP_OF_MILLISECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond) new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond)
.AddMilliseconds(increment), .AddMilliseconds(increment),
Periodicity.TOP_OF_SECOND => Periodicity.TOP_OF_SECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second) new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second)
.AddSeconds(increment), .AddSeconds(increment),
Periodicity.TOP_OF_MINUTE => Periodicity.TOP_OF_MINUTE =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, 0) new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, 0)
.AddMinutes(increment), .AddMinutes(increment),
Periodicity.TOP_OF_HOUR => Periodicity.TOP_OF_HOUR =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0) new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0)
.AddHours(increment), .AddHours(increment),
Periodicity.TOP_OF_DAY => Periodicity.TOP_OF_DAY =>
new DateTime(input.Year, input.Month, input.Day) new DateTime(input.Year, input.Month, input.Day)
.AddDays(increment), .AddDays(increment),
_ => throw new Exception("invalid periodicity type: " + this.PeriodicityType), _ => throw new Exception("invalid periodicity type: " + this.PeriodicityType),
}; };
public Periodicity PeriodicityType { get; set; } public Periodicity PeriodicityType { get; set; }
public bool ShouldRoll public bool ShouldRoll
{ {
get get
{ {
DateTime now = DateTime.Now; DateTime now = DateTime.Now;
if (now > this.nextRoll) if (now > this.nextRoll)
{ {
this.currentRoll = now; this.currentRoll = now;
this.nextRoll = this.NextTriggeringTime(now, this.period); this.nextRoll = this.NextTriggeringTime(now, this.period);
return true; return true;
} }
return false; return false;
} }
} }
public string Format => this.currentRoll.ToString(this.format); 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> <ItemGroup>
<PackageReference Include="log4net" Version="2.0.8" /> <PackageReference Include="log4net" Version="2.0.8" />
<PackageReference Include="YamlDotNet" Version="8.1.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.*"> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

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

@ -1,227 +1,227 @@
using System; using System;
using System.Management; using System.Management;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using DynamicProxy; using DynamicProxy;
namespace WMI namespace WMI
{ {
// https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-service // https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-service
public enum ReturnValue : uint public enum ReturnValue : uint
{ {
Success = 0, Success = 0,
NotSupported = 1, NotSupported = 1,
AccessDenied = 2, AccessDenied = 2,
DependentServicesRunning = 3, DependentServicesRunning = 3,
InvalidServiceControl = 4, InvalidServiceControl = 4,
ServiceCannotAcceptControl = 5, ServiceCannotAcceptControl = 5,
ServiceNotActive = 6, ServiceNotActive = 6,
ServiceRequestTimeout = 7, ServiceRequestTimeout = 7,
UnknownFailure = 8, UnknownFailure = 8,
PathNotFound = 9, PathNotFound = 9,
ServiceAlreadyRunning = 10, ServiceAlreadyRunning = 10,
ServiceDatabaseLocked = 11, ServiceDatabaseLocked = 11,
ServiceDependencyDeleted = 12, ServiceDependencyDeleted = 12,
ServiceDependencyFailure = 13, ServiceDependencyFailure = 13,
ServiceDisabled = 14, ServiceDisabled = 14,
ServiceLogonFailure = 15, ServiceLogonFailure = 15,
ServiceMarkedForDeletion = 16, ServiceMarkedForDeletion = 16,
ServiceNoThread = 17, ServiceNoThread = 17,
StatusCircularDependency = 18, StatusCircularDependency = 18,
StatusDuplicateName = 19, StatusDuplicateName = 19,
StatusInvalidName = 20, StatusInvalidName = 20,
StatusInvalidParameter = 21, StatusInvalidParameter = 21,
StatusInvalidServiceAccount = 22, StatusInvalidServiceAccount = 22,
StatusServiceExists = 23, StatusServiceExists = 23,
ServiceAlreadyPaused = 24, ServiceAlreadyPaused = 24,
NoSuchService = 200 NoSuchService = 200
} }
/// <summary> /// <summary>
/// Signals a problem in WMI related operations /// Signals a problem in WMI related operations
/// </summary> /// </summary>
public class WmiException : Exception public class WmiException : Exception
{ {
public readonly ReturnValue ErrorCode; public readonly ReturnValue ErrorCode;
public WmiException(string message, ReturnValue code) public WmiException(string message, ReturnValue code)
: base(message) : base(message)
{ {
this.ErrorCode = code; this.ErrorCode = code;
} }
public WmiException(ReturnValue code) public WmiException(ReturnValue code)
: this(code.ToString(), code) : this(code.ToString(), code)
{ {
} }
} }
/// <summary> /// <summary>
/// Associated a WMI class name to the proxy interface (which should extend from IWmiCollection) /// Associated a WMI class name to the proxy interface (which should extend from IWmiCollection)
/// </summary> /// </summary>
public class WmiClassName : Attribute public class WmiClassName : Attribute
{ {
public readonly string Name; public readonly string Name;
public WmiClassName(string name) => this.Name = name; public WmiClassName(string name) => this.Name = name;
} }
/// <summary> /// <summary>
/// Marker interface to denote a collection in WMI. /// Marker interface to denote a collection in WMI.
/// </summary> /// </summary>
public interface IWmiCollection public interface IWmiCollection
{ {
} }
/// <summary> /// <summary>
/// Marker interface to denote an individual managed object /// Marker interface to denote an individual managed object
/// </summary> /// </summary>
public interface IWmiObject public interface IWmiObject
{ {
} }
public sealed class WmiRoot public sealed class WmiRoot
{ {
private readonly ManagementScope wmiScope; private readonly ManagementScope wmiScope;
public WmiRoot() public WmiRoot()
{ {
ConnectionOptions options = new ConnectionOptions ConnectionOptions options = new ConnectionOptions
{ {
EnablePrivileges = true, EnablePrivileges = true,
Impersonation = ImpersonationLevel.Impersonate, Impersonation = ImpersonationLevel.Impersonate,
Authentication = AuthenticationLevel.PacketPrivacy, Authentication = AuthenticationLevel.PacketPrivacy,
}; };
this.wmiScope = new ManagementScope(@"\\.\root\cimv2", options); this.wmiScope = new ManagementScope(@"\\.\root\cimv2", options);
this.wmiScope.Connect(); this.wmiScope.Connect();
} }
private static string Capitalize(string s) private static string Capitalize(string s)
{ {
return char.ToUpper(s[0]) + s.Substring(1); return char.ToUpper(s[0]) + s.Substring(1);
} }
private abstract class BaseHandler : IProxyInvocationHandler private abstract class BaseHandler : IProxyInvocationHandler
{ {
public abstract object? Invoke(object proxy, MethodInfo method, object[] arguments); public abstract object? Invoke(object proxy, MethodInfo method, object[] arguments);
protected void CheckError(ManagementBaseObject result) protected void CheckError(ManagementBaseObject result)
{ {
uint code = (uint)result["returnValue"]; uint code = (uint)result["returnValue"];
if (code != 0) if (code != 0)
{ {
throw new WmiException((ReturnValue)code); throw new WmiException((ReturnValue)code);
} }
} }
protected ManagementBaseObject GetMethodParameters(ManagementObject wmiObject, string methodName, ParameterInfo[] methodParameters, object[] arguments) protected ManagementBaseObject GetMethodParameters(ManagementObject wmiObject, string methodName, ParameterInfo[] methodParameters, object[] arguments)
{ {
ManagementBaseObject wmiParameters = wmiObject.GetMethodParameters(methodName); ManagementBaseObject wmiParameters = wmiObject.GetMethodParameters(methodName);
for (int i = 0; i < arguments.Length; i++) for (int i = 0; i < arguments.Length; i++)
{ {
string capitalizedName = Capitalize(methodParameters[i].Name!); string capitalizedName = Capitalize(methodParameters[i].Name!);
wmiParameters[capitalizedName] = arguments[i]; wmiParameters[capitalizedName] = arguments[i];
} }
return wmiParameters; return wmiParameters;
} }
} }
private class InstanceHandler : BaseHandler, IWmiObject private class InstanceHandler : BaseHandler, IWmiObject
{ {
private readonly ManagementObject wmiObject; private readonly ManagementObject wmiObject;
public InstanceHandler(ManagementObject wmiObject) => this.wmiObject = wmiObject; public InstanceHandler(ManagementObject wmiObject) => this.wmiObject = wmiObject;
public override object? Invoke(object proxy, MethodInfo method, object[] arguments) public override object? Invoke(object proxy, MethodInfo method, object[] arguments)
{ {
if (method.DeclaringType == typeof(IWmiObject)) if (method.DeclaringType == typeof(IWmiObject))
{ {
return method.Invoke(this, arguments); return method.Invoke(this, arguments);
} }
// TODO: proper property support // TODO: proper property support
if (method.Name.StartsWith("set_")) if (method.Name.StartsWith("set_"))
{ {
this.wmiObject[method.Name.Substring(4)] = arguments[0]; this.wmiObject[method.Name.Substring(4)] = arguments[0];
return null; return null;
} }
if (method.Name.StartsWith("get_")) if (method.Name.StartsWith("get_"))
{ {
return this.wmiObject[method.Name.Substring(4)]; return this.wmiObject[method.Name.Substring(4)];
} }
string methodName = method.Name; string methodName = method.Name;
using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null : using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null :
this.GetMethodParameters(this.wmiObject, methodName, method.GetParameters(), arguments); this.GetMethodParameters(this.wmiObject, methodName, method.GetParameters(), arguments);
using ManagementBaseObject result = this.wmiObject.InvokeMethod(methodName, wmiParameters, null); using ManagementBaseObject result = this.wmiObject.InvokeMethod(methodName, wmiParameters, null);
this.CheckError(result); this.CheckError(result);
return null; return null;
} }
} }
private class ClassHandler : BaseHandler private class ClassHandler : BaseHandler
{ {
private readonly ManagementClass wmiClass; private readonly ManagementClass wmiClass;
private readonly string className; private readonly string className;
public ClassHandler(ManagementScope wmiScope, string className) public ClassHandler(ManagementScope wmiScope, string className)
{ {
this.wmiClass = new ManagementClass(wmiScope, new ManagementPath(className), null); this.wmiClass = new ManagementClass(wmiScope, new ManagementPath(className), null);
this.className = className; this.className = className;
} }
public override object? Invoke(object proxy, MethodInfo method, object[] arguments) public override object? Invoke(object proxy, MethodInfo method, object[] arguments)
{ {
ParameterInfo[] methodParameters = method.GetParameters(); ParameterInfo[] methodParameters = method.GetParameters();
if (method.Name == nameof(IWin32Services.Select)) if (method.Name == nameof(IWin32Services.Select))
{ {
// select method to find instances // select method to find instances
StringBuilder query = new StringBuilder("SELECT * FROM ").Append(this.className).Append(" WHERE "); StringBuilder query = new StringBuilder("SELECT * FROM ").Append(this.className).Append(" WHERE ");
for (int i = 0; i < arguments.Length; i++) for (int i = 0; i < arguments.Length; i++)
{ {
if (i != 0) if (i != 0)
{ {
query.Append(" AND "); query.Append(" AND ");
} }
query.Append(' ').Append(Capitalize(methodParameters[i].Name!)).Append(" = '").Append(arguments[i]).Append('\''); 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 ManagementObjectSearcher searcher = new ManagementObjectSearcher(this.wmiClass.Scope, new ObjectQuery(query.ToString()));
using ManagementObjectCollection results = searcher.Get(); using ManagementObjectCollection results = searcher.Get();
// TODO: support collections // TODO: support collections
foreach (ManagementObject wmiObject in results) foreach (ManagementObject wmiObject in results)
{ {
return ProxyFactory.Create(new InstanceHandler(wmiObject), method.ReturnType, true); return ProxyFactory.Create(new InstanceHandler(wmiObject), method.ReturnType, true);
} }
return null; return null;
} }
string methodName = method.Name; string methodName = method.Name;
using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null : using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null :
this.GetMethodParameters(this.wmiClass, methodName, methodParameters, arguments); this.GetMethodParameters(this.wmiClass, methodName, methodParameters, arguments);
using ManagementBaseObject result = this.wmiClass.InvokeMethod(methodName, wmiParameters, null); using ManagementBaseObject result = this.wmiClass.InvokeMethod(methodName, wmiParameters, null);
this.CheckError(result); this.CheckError(result);
return null; return null;
} }
} }
/// <summary> /// <summary>
/// Obtains an object that corresponds to a table in WMI, which is a collection of a managed object. /// Obtains an object that corresponds to a table in WMI, which is a collection of a managed object.
/// </summary> /// </summary>
public T GetCollection<T>() public T GetCollection<T>()
where T : IWmiCollection where T : IWmiCollection
{ {
WmiClassName className = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0]; WmiClassName className = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0];
return (T)ProxyFactory.Create(new ClassHandler(this.wmiScope, className.Name), typeof(T), true); 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 namespace WMI
{ {
public enum ServiceType public enum ServiceType
{ {
KernalDriver = 1, KernalDriver = 1,
FileSystemDriver = 2, FileSystemDriver = 2,
Adapter = 4, Adapter = 4,
RecognizerDriver = 8, RecognizerDriver = 8,
OwnProcess = 16, OwnProcess = 16,
ShareProcess = 32, ShareProcess = 32,
InteractiveProcess = 256, InteractiveProcess = 256,
} }
public enum ErrorControl public enum ErrorControl
{ {
UserNotNotified = 0, UserNotNotified = 0,
UserNotified = 1, UserNotified = 1,
SystemRestartedWithLastKnownGoodConfiguration = 2, SystemRestartedWithLastKnownGoodConfiguration = 2,
SystemAttemptsToStartWithAGoodConfiguration = 3 SystemAttemptsToStartWithAGoodConfiguration = 3
} }
public enum StartMode public enum StartMode
{ {
/// <summary> /// <summary>
/// Device driver started by the operating system loader. This value is valid only for driver services. /// Device driver started by the operating system loader. This value is valid only for driver services.
/// </summary> /// </summary>
Boot, Boot,
/// <summary> /// <summary>
/// Device driver started by the operating system initialization process. This value is valid only for driver services. /// Device driver started by the operating system initialization process. This value is valid only for driver services.
/// </summary> /// </summary>
System, System,
/// <summary> /// <summary>
/// Service to be started automatically by the Service Control Manager during system startup. /// Service to be started automatically by the Service Control Manager during system startup.
/// </summary> /// </summary>
Automatic, Automatic,
/// <summary> /// <summary>
/// Service to be started by the Service Control Manager when a process calls the StartService method. /// Service to be started by the Service Control Manager when a process calls the StartService method.
/// </summary> /// </summary>
Manual, Manual,
/// <summary> /// <summary>
/// Service that can no longer be started. /// Service that can no longer be started.
/// </summary> /// </summary>
Disabled, Disabled,
} }
[WmiClassName("Win32_Service")] [WmiClassName("Win32_Service")]
public interface IWin32Services : IWmiCollection public interface IWin32Services : IWmiCollection
{ {
// ReturnValue Create(bool desktopInteract, string displayName, int errorControl, string loadOrderGroup, string loadOrderGroupDependencies, string name, string pathName, string serviceDependencies, string serviceType, string startMode, string startName, string startPassword); // 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? startName, string? startPassword, string[] serviceDependencies);
void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string[] serviceDependencies); void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string[] serviceDependencies);
IWin32Service? Select(string name); IWin32Service? Select(string name);
} }
// https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service // https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service
public interface IWin32Service : IWmiObject public interface IWin32Service : IWmiObject
{ {
bool Started { get; } bool Started { get; }
void Delete(); void Delete();
void StartService(); void StartService();
void StopService(); void StopService();
} }
} }

View File

@ -5,6 +5,7 @@ using System.IO;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using log4net; using log4net;
using WinSW.Configuration;
using WinSW.Extensions; using WinSW.Extensions;
using WinSW.Util; using WinSW.Util;
using static WinSW.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods; using static WinSW.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods;
@ -179,7 +180,7 @@ namespace WinSW.Plugins.RunawayProcessKiller
return parameters.Environment; 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 // We expect the upper logic to process any errors
// TODO: a better parser API for types would be useful // TODO: a better parser API for types would be useful

View File

@ -1,9 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml; using System.Xml;
using log4net; using log4net;
using WinSW.Configuration;
using WinSW.Extensions; using WinSW.Extensions;
using WinSW.Util; using WinSW.Util;
namespace WinSW.Plugins.SharedDirectoryMapper namespace WinSW.Plugins.SharedDirectoryMapper
{ {
public class SharedDirectoryMapper : AbstractWinSWExtension public class SharedDirectoryMapper : AbstractWinSWExtension
@ -25,7 +27,7 @@ namespace WinSW.Plugins.SharedDirectoryMapper
this._entries.Add(config); 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"); XmlNodeList? mapNodes = XmlHelper.SingleNode(node, "mapping", false)!.SelectNodes("map");
if (mapNodes != null) if (mapNodes != null)

View File

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

View File

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