mirror of https://github.com/winsw/winsw
Update Yaml Configuration support (#596)
* Sync with upstream (#3) * Add a Dependabot configuration (#558) * Add a Dependabot configuration * Update dependabot.yml * Bump NUnit3TestAdapter from 3.16.0 to 3.16.1 (#561) Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.16.0 to 3.16.1. - [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases) - [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.16...V3.16.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump Microsoft.NET.Test.Sdk from 16.4.0 to 16.6.1 (#563) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.4.0 to 16.6.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.4.0...v16.6.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump ilmerge from 3.0.29 to 3.0.40 (#559) * Bump ilmerge from 3.0.29 to 3.0.40 Bumps [ilmerge](https://github.com/dotnet/ILMerge) from 3.0.29 to 3.0.40. - [Release notes](https://github.com/dotnet/ILMerge/releases) - [Commits](https://github.com/dotnet/ILMerge/commits) Signed-off-by: dependabot[bot] <support@github.com> * Define $(ILMergeVersion) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com> * Bump coverlet.collector from 1.2.0 to 1.3.0 (#560) Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 1.2.0 to 1.3.0. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump ilmerge from 3.0.40 to 3.0.41 (#571) Bumps [ilmerge](https://github.com/dotnet/ILMerge) from 3.0.40 to 3.0.41. - [Release notes](https://github.com/dotnet/ILMerge/releases) - [Commits](https://github.com/dotnet/ILMerge/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oleg Nenashev <o.v.nenashev@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com> * Update WinSW Yaml Support Add sample-allOption.yml Update timespan values to aprse from strings. Now user can specify timespan values like 5sec. Implement unimplemented properties in YmlConfiguration * Update configurations namings * Move ParseTimeSpan to seperate utility class * Update Environment Variable Syntax Now env variables support name: value: syntax * Add YamlConfigFile.md Add yaml user documentation Update YamlSupport Unit test * Update YamlConfigFile.md Summerize the yaml documentation and reference the XmlConfigFile.md for more details * Update YamlDoc * Update doc/YamlConfigFile.md Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com> * Update yaml configurations to expand environment variables * Update doc/YamlConfigFile.md Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com> Co-authored-by: Oleg Nenashev <o.v.nenashev@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com>pull/580/head^2
parent
7554d0ecbd
commit
13cfe45490
|
@ -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
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,6 @@ namespace WinSW.Configuration
|
||||||
// Extensions
|
// Extensions
|
||||||
XmlNode? ExtensionsConfiguration { get; }
|
XmlNode? ExtensionsConfiguration { get; }
|
||||||
|
|
||||||
// IWinSWConfiguration Support
|
|
||||||
List<string> ExtensionIds { get; }
|
List<string> ExtensionIds { get; }
|
||||||
|
|
||||||
// Service Account
|
// Service Account
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace WinSW.Configuration
|
namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,7 @@ namespace WinSW.Configuration
|
||||||
[YamlMember(Alias = "domain")]
|
[YamlMember(Alias = "domain")]
|
||||||
public string? ServiceAccountDomain { get; set; }
|
public string? ServiceAccountDomain { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "Password")]
|
[YamlMember(Alias = "password")]
|
||||||
public string? ServiceAccountPassword { get; set; }
|
public string? ServiceAccountPassword { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "allowservicelogon")]
|
[YamlMember(Alias = "allowservicelogon")]
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using WinSW.Native;
|
using WinSW.Native;
|
||||||
|
using WinSW.Util;
|
||||||
using WMI;
|
using WMI;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using static WinSW.Download;
|
using static WinSW.Download;
|
||||||
|
@ -28,9 +30,6 @@ namespace WinSW.Configuration
|
||||||
[YamlMember(Alias = "executablePath")]
|
[YamlMember(Alias = "executablePath")]
|
||||||
public string? ExecutablePathYaml { get; set; }
|
public string? ExecutablePathYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "caption")]
|
|
||||||
public string? CaptionYaml { get; set; }
|
|
||||||
|
|
||||||
[YamlMember(Alias = "hideWindow")]
|
[YamlMember(Alias = "hideWindow")]
|
||||||
public bool? HideWindowYaml { get; set; }
|
public bool? HideWindowYaml { get; set; }
|
||||||
|
|
||||||
|
@ -62,36 +61,36 @@ namespace WinSW.Configuration
|
||||||
public bool? StopParentProcessFirstYaml { get; set; }
|
public bool? StopParentProcessFirstYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "resetFailureAfter")]
|
[YamlMember(Alias = "resetFailureAfter")]
|
||||||
public TimeSpan? ResetFailureAfterYaml { get; set; }
|
public string? ResetFailureAfterYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "stopTimeout")]
|
[YamlMember(Alias = "stopTimeout")]
|
||||||
public TimeSpan? StopTimeoutYaml { get; set; }
|
public string? StopTimeoutYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "startMode")]
|
[YamlMember(Alias = "startMode")]
|
||||||
public StartMode? StartModeYaml { get; set; }
|
public string? StartModeYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "serviceDependencies")]
|
[YamlMember(Alias = "serviceDependencies")]
|
||||||
public string[]? ServiceDependenciesYaml { get; set; }
|
public string[]? ServiceDependenciesYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "waitHint")]
|
[YamlMember(Alias = "waitHint")]
|
||||||
public TimeSpan? WaitHintYaml { get; set; }
|
public string? WaitHintYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "sleepTime")]
|
[YamlMember(Alias = "sleepTime")]
|
||||||
public TimeSpan? SleepTimeYaml { get; set; }
|
public string? SleepTimeYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "interactive")]
|
[YamlMember(Alias = "interactive")]
|
||||||
public bool? InteractiveYaml { get; set; }
|
public bool? InteractiveYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "priority")]
|
[YamlMember(Alias = "priority")]
|
||||||
public ProcessPriorityClass? PriorityYaml { get; set; }
|
public string? PriorityYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "beepOnShutdown")]
|
[YamlMember(Alias = "beepOnShutdown")]
|
||||||
public bool BeepOnShutdown { get; set; }
|
public bool BeepOnShutdown { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "env")]
|
[YamlMember(Alias = "env")]
|
||||||
public Dictionary<string, string>? EnvironmentVariablesYaml { get; set; }
|
public List<YamlEnv>? EnvironmentVariablesYaml { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "failureActions")]
|
[YamlMember(Alias = "onFailure")]
|
||||||
public List<YamlFailureAction>? YamlFailureActions { get; set; }
|
public List<YamlFailureAction>? YamlFailureActions { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "delayedAutoStart")]
|
[YamlMember(Alias = "delayedAutoStart")]
|
||||||
|
@ -100,6 +99,18 @@ namespace WinSW.Configuration
|
||||||
[YamlMember(Alias = "securityDescriptor")]
|
[YamlMember(Alias = "securityDescriptor")]
|
||||||
public string? SecurityDescriptorYaml { get; set; }
|
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
|
public class YamlLog : Log
|
||||||
{
|
{
|
||||||
private readonly YamlConfiguration configs;
|
private readonly YamlConfiguration configs;
|
||||||
|
@ -163,7 +174,7 @@ namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
return this.NameYamlLog is null ?
|
return this.NameYamlLog is null ?
|
||||||
DefaultWinSWSettings.DefaultLogSettings.Name :
|
DefaultWinSWSettings.DefaultLogSettings.Name :
|
||||||
Environment.ExpandEnvironmentVariables(this.NameYamlLog);
|
ExpandEnv(this.NameYamlLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +184,7 @@ namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
return this.LogPathYamlLog is null ?
|
return this.LogPathYamlLog is null ?
|
||||||
DefaultWinSWSettings.DefaultLogSettings.Directory :
|
DefaultWinSWSettings.DefaultLogSettings.Directory :
|
||||||
Environment.ExpandEnvironmentVariables(this.LogPathYamlLog);
|
ExpandEnv(this.LogPathYamlLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +249,7 @@ namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
return this.OutFilePatternYamlLog is null ?
|
return this.OutFilePatternYamlLog is null ?
|
||||||
DefaultWinSWSettings.DefaultLogSettings.OutFilePattern :
|
DefaultWinSWSettings.DefaultLogSettings.OutFilePattern :
|
||||||
Environment.ExpandEnvironmentVariables(this.OutFilePatternYamlLog);
|
ExpandEnv(this.OutFilePatternYamlLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +259,7 @@ namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
return this.ErrFilePatternYamlLog is null ?
|
return this.ErrFilePatternYamlLog is null ?
|
||||||
DefaultWinSWSettings.DefaultLogSettings.ErrFilePattern :
|
DefaultWinSWSettings.DefaultLogSettings.ErrFilePattern :
|
||||||
Environment.ExpandEnvironmentVariables(this.ErrFilePatternYamlLog);
|
ExpandEnv(this.ErrFilePatternYamlLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +306,7 @@ namespace WinSW.Configuration
|
||||||
public string ToYamlDownload { get; set; } = string.Empty;
|
public string ToYamlDownload { get; set; } = string.Empty;
|
||||||
|
|
||||||
[YamlMember(Alias = "auth")]
|
[YamlMember(Alias = "auth")]
|
||||||
public AuthType AuthYamlDownload { get; set; }
|
public string? AuthYamlDownload { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "username")]
|
[YamlMember(Alias = "username")]
|
||||||
public string? UsernameYamlDownload { get; set; }
|
public string? UsernameYamlDownload { get; set; }
|
||||||
|
@ -311,19 +322,71 @@ namespace WinSW.Configuration
|
||||||
|
|
||||||
[YamlMember(Alias = "proxy")]
|
[YamlMember(Alias = "proxy")]
|
||||||
public string? ProxyYamlDownload { get; set; }
|
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
|
public class YamlFailureAction
|
||||||
{
|
{
|
||||||
[YamlMember(Alias = "type")]
|
[YamlMember(Alias = "action")]
|
||||||
private SC_ACTION_TYPE type;
|
public string? FailureAction { get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "delay")]
|
[YamlMember(Alias = "delay")]
|
||||||
private TimeSpan delay;
|
public string? FailureActionDelay { get; set; }
|
||||||
|
|
||||||
public SC_ACTION_TYPE Type { get => this.type; set => this.type = value; }
|
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)
|
||||||
|
};
|
||||||
|
|
||||||
public TimeSpan Delay { get => this.delay; set => this.delay = value; }
|
return actionType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan Delay => this.FailureActionDelay is null ? TimeSpan.Zero : ConfigHelper.ParseTimeSpan(this.FailureActionDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? GetArguments(string? args, ArgType type)
|
private string? GetArguments(string? args, ArgType type)
|
||||||
|
@ -343,7 +406,7 @@ namespace WinSW.Configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Environment.ExpandEnvironmentVariables(args);
|
return ExpandEnv(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ArgType
|
private enum ArgType
|
||||||
|
@ -365,28 +428,35 @@ namespace WinSW.Configuration
|
||||||
foreach (var item in downloads)
|
foreach (var item in downloads)
|
||||||
{
|
{
|
||||||
result.Add(new Download(
|
result.Add(new Download(
|
||||||
item.FromYamlDownload,
|
item.FromDownload,
|
||||||
item.ToYamlDownload,
|
item.ToDownload,
|
||||||
item.FailOnErrorYamlDownload,
|
item.FailOnErrorYamlDownload,
|
||||||
item.AuthYamlDownload,
|
item.AuthDownload,
|
||||||
item.UsernameYamlDownload,
|
item.UsernameDownload,
|
||||||
item.PasswordYamlDownload,
|
item.PasswordDownload,
|
||||||
item.UnsecureAuthYamlDownload,
|
item.UnsecureAuthYamlDownload,
|
||||||
item.ProxyYamlDownload));
|
item.ProxyDownload));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id => this.IdYaml is null ? this.Defaults.Id : this.IdYaml;
|
internal static string ExpandEnv(string str)
|
||||||
|
{
|
||||||
|
return Environment.ExpandEnvironmentVariables(str);
|
||||||
|
}
|
||||||
|
|
||||||
public string Description => this.DescriptionYaml is null ? this.Defaults.Description : this.DescriptionYaml;
|
public string Id => this.IdYaml is null ? this.Defaults.Id : ExpandEnv(this.IdYaml);
|
||||||
|
|
||||||
public string Executable => this.ExecutableYaml is null ? this.Defaults.Executable : this.ExecutableYaml;
|
public string Description => this.DescriptionYaml is null ? this.Defaults.Description : ExpandEnv(this.DescriptionYaml);
|
||||||
|
|
||||||
public string ExecutablePath => this.ExecutablePathYaml is null ? this.Defaults.ExecutablePath : this.ExecutablePathYaml;
|
public string Executable => this.ExecutableYaml is null ? this.Defaults.Executable : ExpandEnv(this.ExecutableYaml);
|
||||||
|
|
||||||
public string Caption => this.CaptionYaml is null ? this.Defaults.Caption : this.CaptionYaml;
|
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 HideWindow => this.HideWindowYaml is null ? this.Defaults.HideWindow : (bool)this.HideWindowYaml;
|
||||||
|
|
||||||
|
@ -400,7 +470,33 @@ namespace WinSW.Configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public StartMode StartMode => this.StartModeYaml is null ? this.Defaults.StartMode : (StartMode)this.StartModeYaml;
|
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
|
public string Arguments
|
||||||
{
|
{
|
||||||
|
@ -421,7 +517,7 @@ namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
return this.StopExecutableYaml is null ?
|
return this.StopExecutableYaml is null ?
|
||||||
this.Defaults.StopExecutable :
|
this.Defaults.StopExecutable :
|
||||||
null;
|
ExpandEnv(this.StopExecutableYaml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,23 +543,65 @@ namespace WinSW.Configuration
|
||||||
|
|
||||||
public TimeSpan ResetFailureAfter => this.ResetFailureAfterYaml is null ?
|
public TimeSpan ResetFailureAfter => this.ResetFailureAfterYaml is null ?
|
||||||
this.Defaults.ResetFailureAfter :
|
this.Defaults.ResetFailureAfter :
|
||||||
(TimeSpan)this.ResetFailureAfterYaml;
|
ConfigHelper.ParseTimeSpan(this.ResetFailureAfterYaml);
|
||||||
|
|
||||||
public string WorkingDirectory => this.WorkingDirectoryYaml is null ?
|
public string WorkingDirectory => this.WorkingDirectoryYaml is null ?
|
||||||
this.Defaults.WorkingDirectory :
|
this.Defaults.WorkingDirectory :
|
||||||
this.WorkingDirectoryYaml;
|
ExpandEnv(this.WorkingDirectoryYaml);
|
||||||
|
|
||||||
public ProcessPriorityClass Priority => this.PriorityYaml is null ? this.Defaults.Priority : (ProcessPriorityClass)this.PriorityYaml;
|
public ProcessPriorityClass Priority
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.PriorityYaml is null)
|
||||||
|
{
|
||||||
|
return this.Defaults.Priority;
|
||||||
|
}
|
||||||
|
|
||||||
public TimeSpan StopTimeout => this.StopTimeoutYaml is null ? this.Defaults.StopTimeout : (TimeSpan)this.StopTimeoutYaml;
|
var p = ExpandEnv(this.PriorityYaml);
|
||||||
|
|
||||||
public string[] ServiceDependencies => this.ServiceDependenciesYaml is null ?
|
try
|
||||||
this.Defaults.ServiceDependencies :
|
{
|
||||||
this.ServiceDependenciesYaml;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
public TimeSpan WaitHint => this.WaitHintYaml is null ? this.Defaults.WaitHint : (TimeSpan)this.WaitHintYaml;
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TimeSpan SleepTime => this.SleepTimeYaml is null ? this.Defaults.SleepTime : (TimeSpan)this.SleepTimeYaml;
|
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 bool Interactive => this.InteractiveYaml is null ? this.Defaults.Interactive : (bool)this.InteractiveYaml;
|
||||||
|
|
||||||
|
@ -481,9 +619,16 @@ namespace WinSW.Configuration
|
||||||
{
|
{
|
||||||
foreach (var item in this.EnvironmentVariablesYaml)
|
foreach (var item in this.EnvironmentVariablesYaml)
|
||||||
{
|
{
|
||||||
var value = Environment.ExpandEnvironmentVariables(item.Value);
|
if (item.Name is null || item.Value is null)
|
||||||
this.EnvironmentVariables[item.Key] = value;
|
{
|
||||||
Environment.SetEnvironmentVariable(item.Key, value);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = item.Name;
|
||||||
|
var value = ExpandEnv(item.Value);
|
||||||
|
|
||||||
|
this.EnvironmentVariables[key] = value;
|
||||||
|
Environment.SetEnvironmentVariable(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -496,19 +641,26 @@ namespace WinSW.Configuration
|
||||||
|
|
||||||
public string LogMode => this.Log.Mode is null ? this.Defaults.LogMode : this.Log.Mode;
|
public string LogMode => this.Log.Mode is null ? this.Defaults.LogMode : this.Log.Mode;
|
||||||
|
|
||||||
// TODO
|
// TODO - Extensions
|
||||||
XmlNode? IWinSWConfiguration.ExtensionsConfiguration => throw new NotImplementedException();
|
XmlNode? IWinSWConfiguration.ExtensionsConfiguration => throw new NotImplementedException();
|
||||||
|
|
||||||
public List<string> ExtensionIds => throw new NotImplementedException();
|
public List<string> ExtensionIds => this.YamlExtensionIds ?? this.Defaults.ExtensionIds;
|
||||||
|
|
||||||
public string BaseName => throw new NotImplementedException();
|
public string BaseName => this.Defaults.BaseName;
|
||||||
|
|
||||||
public string BasePath => throw new NotImplementedException();
|
public string BasePath => this.Defaults.BasePath;
|
||||||
|
|
||||||
public string? ServiceAccountDomain => throw new NotImplementedException();
|
public string? SecurityDescriptor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.SecurityDescriptorYaml is null)
|
||||||
|
{
|
||||||
|
return this.Defaults.SecurityDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
public string? ServiceAccountName => throw new NotImplementedException();
|
return ExpandEnv(this.SecurityDescriptorYaml);
|
||||||
|
}
|
||||||
public string? SecurityDescriptor => throw new NotImplementedException();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,38 +150,9 @@ namespace WinSW
|
||||||
private TimeSpan SingleTimeSpanElement(string tagName, TimeSpan defaultValue)
|
private TimeSpan SingleTimeSpanElement(string tagName, TimeSpan defaultValue)
|
||||||
{
|
{
|
||||||
string? value = this.SingleElement(tagName, true);
|
string? value = this.SingleElement(tagName, true);
|
||||||
return value is null ? defaultValue : this.ParseTimeSpan(value);
|
return value is null ? defaultValue : ConfigHelper.ParseTimeSpan(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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 }
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path to the executable.
|
/// Path to the executable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -647,7 +618,7 @@ namespace WinSW
|
||||||
_ => throw new Exception("Invalid failure action: " + action)
|
_ => throw new Exception("Invalid failure action: " + action)
|
||||||
};
|
};
|
||||||
XmlAttribute? delay = node.Attributes["delay"];
|
XmlAttribute? delay = node.Attributes["delay"];
|
||||||
result[i] = new SC_ACTION(type, delay != null ? this.ParseTimeSpan(delay.Value) : TimeSpan.Zero);
|
result[i] = new SC_ACTION(type, delay != null ? ConfigHelper.ParseTimeSpan(delay.Value) : TimeSpan.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using WinSW.Configuration;
|
using WinSW.Configuration;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
@ -11,13 +11,9 @@ namespace WinSW
|
||||||
|
|
||||||
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
|
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
|
||||||
|
|
||||||
public string BasePath { get; set; }
|
|
||||||
|
|
||||||
public virtual string ExecutablePath => Defaults.ExecutablePath;
|
|
||||||
|
|
||||||
public ServiceDescriptorYaml()
|
public ServiceDescriptorYaml()
|
||||||
{
|
{
|
||||||
string p = this.ExecutablePath;
|
string p = Defaults.ExecutablePath;
|
||||||
string baseName = Path.GetFileNameWithoutExtension(p);
|
string baseName = Path.GetFileNameWithoutExtension(p);
|
||||||
if (baseName.EndsWith(".vshost"))
|
if (baseName.EndsWith(".vshost"))
|
||||||
{
|
{
|
||||||
|
@ -40,9 +36,9 @@ namespace WinSW
|
||||||
d = d.Parent;
|
d = d.Parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.BasePath = Path.Combine(d.FullName, baseName);
|
var basepath = Path.Combine(d.FullName, baseName);
|
||||||
|
|
||||||
using (var reader = new StreamReader(this.BasePath + ".yml"))
|
using (var reader = new StreamReader(basepath + ".yml"))
|
||||||
{
|
{
|
||||||
var file = reader.ReadToEnd();
|
var file = reader.ReadToEnd();
|
||||||
var deserializer = new DeserializerBuilder().Build();
|
var deserializer = new DeserializerBuilder().Build();
|
||||||
|
@ -56,7 +52,7 @@ namespace WinSW
|
||||||
Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Id);
|
Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Id);
|
||||||
|
|
||||||
// New name
|
// New name
|
||||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
|
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, Defaults.ExecutablePath);
|
||||||
|
|
||||||
// Also inject system environment variables
|
// Also inject system environment variables
|
||||||
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Id);
|
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Id);
|
||||||
|
|
|
@ -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 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,54 +1,46 @@
|
||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using WinSW;
|
using WinSW;
|
||||||
|
using WinSW.Configuration;
|
||||||
|
using WinSW.Native;
|
||||||
|
|
||||||
namespace winswTests
|
namespace winswTests
|
||||||
{
|
{
|
||||||
class ServiceDescriptorYamlTest
|
class ServiceDescriptorYamlTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private string MinimalYaml = @"id: myapp
|
private readonly string MinimalYaml = @"id: myapp
|
||||||
caption: This is a test
|
name: This is a test
|
||||||
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
|
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
|
||||||
description: This is test winsw";
|
description: This is test winsw";
|
||||||
|
|
||||||
|
private readonly DefaultWinSWSettings Defaults = new DefaultWinSWSettings();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Simple_yaml_parsing_test()
|
public void Parse_must_implemented_value_test()
|
||||||
{
|
{
|
||||||
var configs = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations;
|
var yml = @"name: This is a test
|
||||||
|
|
||||||
Assert.AreEqual("myapp", configs.Id);
|
|
||||||
Assert.AreEqual("This is a test", configs.Caption);
|
|
||||||
Assert.AreEqual("C:\\Program Files\\Java\\jdk1.8.0_241\\bin\\java.exe", configs.Executable);
|
|
||||||
Assert.AreEqual("This is test winsw", configs.Description);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Must_implemented_value_test()
|
|
||||||
{
|
|
||||||
string yml = @"caption: This is a test
|
|
||||||
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
|
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
|
||||||
description: This is test winsw";
|
description: This is test winsw";
|
||||||
|
|
||||||
void getId()
|
Assert.That(() =>
|
||||||
{
|
{
|
||||||
var id = ServiceDescriptorYaml.FromYaml(yml).Configurations.Id;
|
_ = ServiceDescriptorYaml.FromYaml(yml).Configurations.Id;
|
||||||
}
|
}, Throws.TypeOf<InvalidOperationException>());
|
||||||
|
|
||||||
Assert.That(() => getId(), Throws.TypeOf<InvalidOperationException>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Default_value_map_test()
|
public void Default_value_map_test()
|
||||||
{
|
{
|
||||||
var executablePath = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations.ExecutablePath;
|
var configs = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations;
|
||||||
|
|
||||||
Assert.IsNotNull(executablePath);
|
Assert.IsNotNull(configs.ExecutablePath);
|
||||||
|
Assert.IsNotNull(configs.BaseName);
|
||||||
|
Assert.IsNotNull(configs.BasePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Simple_download_parsing_test()
|
public void Parse_downloads()
|
||||||
{
|
{
|
||||||
var yml = @"download:
|
var yml = @"download:
|
||||||
-
|
-
|
||||||
|
@ -64,56 +56,94 @@ description: This is test winsw";
|
||||||
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
|
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
|
||||||
|
|
||||||
Assert.AreEqual(3, configs.Downloads.Count);
|
Assert.AreEqual(3, configs.Downloads.Count);
|
||||||
|
Assert.AreEqual("www.sample.com", configs.Downloads[0].From);
|
||||||
|
Assert.AreEqual("c://tmp", configs.Downloads[0].To);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Download_not_specified_test()
|
public void Parse_serviceaccount()
|
||||||
{
|
{
|
||||||
var yml = @"id: jenkins
|
var yml = @"id: myapp
|
||||||
name: No Service Account
|
name: winsw
|
||||||
";
|
description: yaml test
|
||||||
|
executable: java
|
||||||
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
|
|
||||||
|
|
||||||
Assert.DoesNotThrow(() =>
|
|
||||||
{
|
|
||||||
var dowloads = configs.Downloads;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Service_account_not_specified_test()
|
|
||||||
{
|
|
||||||
var yml = @"id: jenkins
|
|
||||||
name: No Service Account
|
|
||||||
";
|
|
||||||
|
|
||||||
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
|
|
||||||
|
|
||||||
Assert.DoesNotThrow(() =>
|
|
||||||
{
|
|
||||||
var serviceAccount = configs.ServiceAccount.AllowServiceAcountLogonRight;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Service_account_specified_but_fields_not_specified()
|
|
||||||
{
|
|
||||||
var yml = @"id: jenkins
|
|
||||||
name: No Service Account
|
|
||||||
serviceaccount:
|
serviceaccount:
|
||||||
user: testuser
|
user: testuser
|
||||||
";
|
domain: mydomain
|
||||||
|
password: pa55w0rd
|
||||||
|
allowservicelogon: yes";
|
||||||
|
|
||||||
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
|
var serviceAccount = ServiceDescriptorYaml.FromYaml(yml).Configurations.ServiceAccount;
|
||||||
|
|
||||||
Assert.DoesNotThrow(() =>
|
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 user = configs.ServiceAccount.ServiceAccountUser;
|
var yml = @"id: myapp
|
||||||
var password = configs.ServiceAccount.ServiceAccountPassword;
|
name: WinSW
|
||||||
var allowLogon = configs.ServiceAccount.AllowServiceAcountLogonRight;
|
executable: java
|
||||||
var hasAccount = configs.ServiceAccount.HasServiceAccount();
|
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)));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue