mirror of https://github.com/winsw/winsw
Merge branch 'master' into filelen
commit
62d10fe2fb
|
@ -0,0 +1,4 @@
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project: off
|
||||||
|
patch: off
|
|
@ -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
|
||||||
|
```
|
|
@ -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
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
174
src/winsw.sln
174
src/winsw.sln
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue