Reorganize projects

pull/588/head
NextTurn 2020-02-19 00:00:00 -08:00 committed by Next Turn
parent 35af3bf78d
commit bbb3cd578c
74 changed files with 2349 additions and 2437 deletions

View File

@ -1,3 +0,0 @@
# Release notes
This content has been moved to the [Releases](https://github.com/winsw/winsw/releases).

View File

@ -64,8 +64,6 @@ User documentation:
* Use-cases:
* [Self-restarting services](docs/self-restarting-service.md)
* [Deferred file operations](docs/deferred-file-operations.md)
* Configuration Management:
* [Puppet Forge Module](docs/puppetWinSW.md)
Developer documentation:

View File

@ -28,10 +28,10 @@ jobs:
BuildConfiguration: Release
steps:
- script: |
dotnet build -c $(BuildConfiguration) src\winsw.sln -p:Version=$(BuildVersion)
dotnet publish -c $(BuildConfiguration) -f netcoreapp3.1 src\Core\ServiceWrapper\winsw.csproj -p:Version=$(BuildVersion)
dotnet publish -c $(BuildConfiguration) -f netcoreapp3.1 -r win-x64 src\Core\ServiceWrapper\winsw.csproj -p:PublishSingleFile=true -p:PublishTrimmed=true -p:Version=$(BuildVersion)
dotnet publish -c $(BuildConfiguration) -f netcoreapp3.1 -r win-x86 src\Core\ServiceWrapper\winsw.csproj -p:PublishSingleFile=true -p:PublishTrimmed=true -p:Version=$(BuildVersion)
dotnet build -c $(BuildConfiguration) src\WinSW.sln -p:Version=$(BuildVersion)
dotnet publish -c $(BuildConfiguration) -f netcoreapp3.1 src\WinSW\WinSW.csproj -p:Version=$(BuildVersion)
dotnet publish -c $(BuildConfiguration) -f netcoreapp3.1 -r win-x64 src\WinSW\WinSW.csproj -p:PublishSingleFile=true -p:PublishTrimmed=true -p:Version=$(BuildVersion)
dotnet publish -c $(BuildConfiguration) -f netcoreapp3.1 -r win-x86 src\WinSW\WinSW.csproj -p:PublishSingleFile=true -p:PublishTrimmed=true -p:Version=$(BuildVersion)
displayName: Build
- task: NuGetToolInstaller@1
displayName: Install Nuget
@ -44,7 +44,7 @@ jobs:
packagesToPack: WinSW.nuspec
versioningScheme: byEnvVar
versionEnvVar: BuildVersion
- script: dotnet test -c $(BuildConfiguration) --no-build src\Test\winswTests\winswTests.csproj
- script: dotnet test -c $(BuildConfiguration) --no-build src\WinSW.Tests\WinSW.Tests.csproj
displayName: Test
- task: PublishBuildArtifacts@1
displayName: Publish .NET 4.6.1

View File

@ -1,3 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("winswTests")]

View File

@ -1,3 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WindowsService")]

View File

@ -1,180 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace WinSW.Plugins.RunawayProcessKiller
{
public partial class RunawayProcessKillerExtension
{
internal static class NativeMethods
{
private const string Kernel32 = "kernel32.dll";
private const string NTDll = "ntdll.dll";
[DllImport(Kernel32)]
internal static extern int IsWow64Process(IntPtr hProcess, out int Wow64Process);
[DllImport(NTDll)]
internal static extern int NtQueryInformationProcess(
IntPtr ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
out PROCESS_BASIC_INFORMATION32 ProcessInformation,
int ProcessInformationLength,
IntPtr ReturnLength = default);
[DllImport(NTDll)]
internal static extern int NtQueryInformationProcess(
IntPtr ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
out PROCESS_BASIC_INFORMATION64 ProcessInformation,
int ProcessInformationLength,
IntPtr ReturnLength = default);
[DllImport(NTDll)]
internal static extern unsafe int NtReadVirtualMemory(
IntPtr ProcessHandle,
IntPtr BaseAddress,
void* Buffer,
IntPtr BufferSize,
IntPtr NumberOfBytesRead = default);
[DllImport(NTDll)]
internal static extern int NtWow64QueryInformationProcess64(
IntPtr ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
out PROCESS_BASIC_INFORMATION64 ProcessInformation,
int ProcessInformationLength,
IntPtr ReturnLength = default);
[DllImport(NTDll)]
internal static extern unsafe int NtWow64ReadVirtualMemory64(
IntPtr ProcessHandle,
long BaseAddress,
void* Buffer,
long BufferSize,
long NumberOfBytesRead = default);
internal enum PROCESSINFOCLASS
{
ProcessBasicInformation = 0,
}
[StructLayout(LayoutKind.Sequential)]
internal readonly struct MEMORY_BASIC_INFORMATION
{
public readonly IntPtr BaseAddress;
private readonly IntPtr AllocationBase;
private readonly uint AllocationProtect;
public readonly IntPtr RegionSize;
private readonly uint State;
private readonly uint Protect;
private readonly uint Type;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PROCESS_BASIC_INFORMATION32
{
private readonly int Reserved1;
public readonly int PebBaseAddress;
private fixed int Reserved2[2];
private readonly uint UniqueProcessId;
private readonly int Reserved3;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PROCESS_BASIC_INFORMATION64
{
private readonly long Reserved1;
public readonly long PebBaseAddress;
private fixed long Reserved2[2];
private readonly ulong UniqueProcessId;
private readonly long Reserved3;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PEB32
{
private fixed byte Reserved1[2];
private readonly byte BeingDebugged;
private fixed byte Reserved2[1];
private fixed int Reserved3[2];
private readonly int Ldr;
public readonly int ProcessParameters;
private fixed int Reserved4[3];
private readonly int AtlThunkSListPtr;
private readonly int Reserved5;
private readonly uint Reserved6;
private readonly int Reserved7;
private readonly uint Reserved8;
private readonly uint AtlThunkSListPtr32;
private fixed int Reserved9[45];
private fixed byte Reserved10[96];
private readonly int PostProcessInitRoutine;
private fixed byte Reserved11[128];
private fixed int Reserved12[1];
private readonly uint SessionId;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PEB64
{
private fixed byte Reserved1[2];
private readonly byte BeingDebugged;
private fixed byte Reserved2[1];
private fixed long Reserved3[2];
private readonly long Ldr;
public readonly long ProcessParameters;
private fixed long Reserved4[3];
private readonly long AtlThunkSListPtr;
private readonly long Reserved5;
private readonly uint Reserved6;
private readonly long Reserved7;
private readonly uint Reserved8;
private readonly uint AtlThunkSListPtr32;
private fixed long Reserved9[45];
private fixed byte Reserved10[96];
private readonly long PostProcessInitRoutine;
private fixed byte Reserved11[128];
private fixed long Reserved12[1];
private readonly uint SessionId;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct RTL_USER_PROCESS_PARAMETERS32
{
private fixed byte Reserved1[16];
private fixed int Reserved2[10];
private readonly UNICODE_STRING32 ImagePathName;
private readonly UNICODE_STRING32 CommandLine;
internal readonly int Environment;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct RTL_USER_PROCESS_PARAMETERS64
{
private fixed byte Reserved1[16];
private fixed long Reserved2[10];
private readonly UNICODE_STRING64 ImagePathName;
private readonly UNICODE_STRING64 CommandLine;
internal readonly long Environment;
}
[StructLayout(LayoutKind.Sequential)]
internal readonly struct UNICODE_STRING32
{
private readonly ushort Length;
private readonly ushort MaximumLength;
private readonly int Buffer;
}
[StructLayout(LayoutKind.Sequential)]
internal readonly struct UNICODE_STRING64
{
private readonly ushort Length;
private readonly ushort MaximumLength;
private readonly long Buffer;
}
}
}
}

View File

@ -1,30 +0,0 @@
using System.Runtime.InteropServices;
namespace WinSW.Plugins.SharedDirectoryMapper
{
internal static class NativeMethods
{
internal const uint RESOURCETYPE_DISK = 0x00000001;
private const string MprLibraryName = "mpr.dll";
[DllImport(MprLibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "WNetAddConnection2W")]
internal static extern int WNetAddConnection2(in NETRESOURCE netResource, string? password = null, string? userName = null, uint flags = 0);
[DllImport(MprLibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "WNetCancelConnection2W")]
internal static extern int WNetCancelConnection2(string name, uint flags = 0, bool force = false);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct NETRESOURCE
{
public uint Scope;
public uint Type;
public uint DisplayType;
public uint Usage;
public string LocalName;
public string RemoteName;
public string Comment;
public string Provider;
}
}
}

View File

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<RootNamespace>winsw.Plugins.SharedDirectoryMapper</RootNamespace>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1'">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\WinSWCore\WinSWCore.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WinSW")]

View File

@ -1,229 +1,229 @@
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using log4net;
using WinSW.Util;
namespace WinSW
{
/// <summary>
/// Specify the download activities prior to the launch.
/// This enables self-updating services.
/// </summary>
public class Download
{
public enum AuthType
{
None = 0,
Sspi,
Basic
}
private static readonly ILog Logger = LogManager.GetLogger(typeof(Download));
public readonly string From;
public readonly string To;
public readonly AuthType Auth;
public readonly string? Username;
public readonly string? Password;
public readonly bool UnsecureAuth;
public readonly bool FailOnError;
public readonly string? Proxy;
public string ShortId => $"(download from {this.From})";
#if NET461
static Download()
{
// If your app runs on .NET Framework 4.7 or later versions, but targets an earlier version
AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", false);
}
#endif
// internal
public Download(
string from,
string to,
bool failOnError = false,
AuthType auth = AuthType.None,
string? username = null,
string? password = null,
bool unsecureAuth = false,
string? proxy = null)
{
this.From = from;
this.To = to;
this.FailOnError = failOnError;
this.Proxy = proxy;
this.Auth = auth;
this.Username = username;
this.Password = password;
this.UnsecureAuth = unsecureAuth;
}
/// <summary>
/// Constructs the download setting sfrom the XML entry
/// </summary>
/// <param name="n">XML element</param>
/// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid</exception>
internal Download(XmlElement n)
{
this.From = XmlHelper.SingleAttribute<string>(n, "from");
this.To = XmlHelper.SingleAttribute<string>(n, "to");
// All arguments below are optional
this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false);
this.Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null);
this.Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.None);
this.Username = XmlHelper.SingleAttribute<string>(n, "user", null);
this.Password = XmlHelper.SingleAttribute<string>(n, "password", null);
this.UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false);
if (this.Auth == AuthType.Basic)
{
// Allow it only for HTTPS or for UnsecureAuth
if (!this.From.StartsWith("https:") && !this.UnsecureAuth)
{
throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + this.ShortId +
"If you really want this you must enable 'unsecureAuth' in the configuration");
}
// Also fail if there is no user/password
if (this.Username is null)
{
throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId);
}
if (this.Password is null)
{
throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId);
}
}
}
// Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest
private void SetBasicAuthHeader(WebRequest request, string username, string password)
{
string authInfo = username + ":" + password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
/// <summary>
/// Downloads the requested file and puts it to the specified target.
/// </summary>
/// <exception cref="WebException">
/// Download failure. FailOnError flag should be processed outside.
/// </exception>
public async Task PerformAsync()
{
WebRequest request = WebRequest.Create(this.From);
if (!string.IsNullOrEmpty(this.Proxy))
{
CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!);
if (proxyInformation.Credentials != null)
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials);
}
else
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress);
}
}
switch (this.Auth)
{
case AuthType.None:
// Do nothing
break;
case AuthType.Sspi:
request.UseDefaultCredentials = true;
request.PreAuthenticate = true;
request.Credentials = CredentialCache.DefaultCredentials;
break;
case AuthType.Basic:
this.SetBasicAuthHeader(request, this.Username!, this.Password!);
break;
default:
throw new WebException("Code defect. Unsupported authentication type: " + this.Auth);
}
bool supportsIfModifiedSince = false;
if (request is HttpWebRequest httpRequest && File.Exists(this.To))
{
supportsIfModifiedSince = true;
httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To);
}
DateTime lastModified = default;
string tmpFilePath = this.To + ".tmp";
try
{
using (WebResponse response = await request.GetResponseAsync())
using (Stream responseStream = response.GetResponseStream())
using (FileStream tmpStream = new FileStream(tmpFilePath, FileMode.Create))
{
if (supportsIfModifiedSince)
{
lastModified = ((HttpWebResponse)response).LastModified;
}
await responseStream.CopyToAsync(tmpStream);
}
FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To);
if (supportsIfModifiedSince)
{
File.SetLastWriteTime(this.To, lastModified);
}
}
catch (WebException e)
{
if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified)
{
Logger.Info($"Skipped downloading unmodified resource '{this.From}'");
}
else
{
throw;
}
}
}
}
public class CustomProxyInformation
{
public string ServerAddress { get; }
public NetworkCredential? Credentials { get; }
public CustomProxyInformation(string proxy)
{
if (proxy.Contains("@"))
{
// Extract proxy credentials
int credsFrom = proxy.IndexOf("://") + 3;
int credsTo = proxy.LastIndexOf("@");
string completeCredsStr = proxy.Substring(credsFrom, credsTo - credsFrom);
int credsSeparator = completeCredsStr.IndexOf(":");
string username = completeCredsStr.Substring(0, credsSeparator);
string password = completeCredsStr.Substring(credsSeparator + 1);
this.Credentials = new NetworkCredential(username, password);
this.ServerAddress = proxy.Replace(completeCredsStr + "@", null);
}
else
{
this.ServerAddress = proxy;
}
}
}
}
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using log4net;
using WinSW.Util;
namespace WinSW
{
/// <summary>
/// Specify the download activities prior to the launch.
/// This enables self-updating services.
/// </summary>
public class Download
{
public enum AuthType
{
None = 0,
Sspi,
Basic
}
private static readonly ILog Logger = LogManager.GetLogger(typeof(Download));
public readonly string From;
public readonly string To;
public readonly AuthType Auth;
public readonly string? Username;
public readonly string? Password;
public readonly bool UnsecureAuth;
public readonly bool FailOnError;
public readonly string? Proxy;
public string ShortId => $"(download from {this.From})";
#if NET461
static Download()
{
// If your app runs on .NET Framework 4.7 or later versions, but targets an earlier version
AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", false);
}
#endif
// internal
public Download(
string from,
string to,
bool failOnError = false,
AuthType auth = AuthType.None,
string? username = null,
string? password = null,
bool unsecureAuth = false,
string? proxy = null)
{
this.From = from;
this.To = to;
this.FailOnError = failOnError;
this.Proxy = proxy;
this.Auth = auth;
this.Username = username;
this.Password = password;
this.UnsecureAuth = unsecureAuth;
}
/// <summary>
/// Constructs the download setting sfrom the XML entry
/// </summary>
/// <param name="n">XML element</param>
/// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid</exception>
internal Download(XmlElement n)
{
this.From = XmlHelper.SingleAttribute<string>(n, "from");
this.To = XmlHelper.SingleAttribute<string>(n, "to");
// All arguments below are optional
this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false);
this.Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null);
this.Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.None);
this.Username = XmlHelper.SingleAttribute<string>(n, "user", null);
this.Password = XmlHelper.SingleAttribute<string>(n, "password", null);
this.UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false);
if (this.Auth == AuthType.Basic)
{
// Allow it only for HTTPS or for UnsecureAuth
if (!this.From.StartsWith("https:") && !this.UnsecureAuth)
{
throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + this.ShortId +
"If you really want this you must enable 'unsecureAuth' in the configuration");
}
// Also fail if there is no user/password
if (this.Username is null)
{
throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId);
}
if (this.Password is null)
{
throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId);
}
}
}
// Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest
private void SetBasicAuthHeader(WebRequest request, string username, string password)
{
string authInfo = username + ":" + password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
/// <summary>
/// Downloads the requested file and puts it to the specified target.
/// </summary>
/// <exception cref="WebException">
/// Download failure. FailOnError flag should be processed outside.
/// </exception>
public async Task PerformAsync()
{
WebRequest request = WebRequest.Create(this.From);
if (!string.IsNullOrEmpty(this.Proxy))
{
CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!);
if (proxyInformation.Credentials != null)
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials);
}
else
{
request.Proxy = new WebProxy(proxyInformation.ServerAddress);
}
}
switch (this.Auth)
{
case AuthType.None:
// Do nothing
break;
case AuthType.Sspi:
request.UseDefaultCredentials = true;
request.PreAuthenticate = true;
request.Credentials = CredentialCache.DefaultCredentials;
break;
case AuthType.Basic:
this.SetBasicAuthHeader(request, this.Username!, this.Password!);
break;
default:
throw new WebException("Code defect. Unsupported authentication type: " + this.Auth);
}
bool supportsIfModifiedSince = false;
if (request is HttpWebRequest httpRequest && File.Exists(this.To))
{
supportsIfModifiedSince = true;
httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To);
}
DateTime lastModified = default;
string tmpFilePath = this.To + ".tmp";
try
{
using (WebResponse response = await request.GetResponseAsync())
using (Stream responseStream = response.GetResponseStream())
using (FileStream tmpStream = new FileStream(tmpFilePath, FileMode.Create))
{
if (supportsIfModifiedSince)
{
lastModified = ((HttpWebResponse)response).LastModified;
}
await responseStream.CopyToAsync(tmpStream);
}
FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To);
if (supportsIfModifiedSince)
{
File.SetLastWriteTime(this.To, lastModified);
}
}
catch (WebException e)
{
if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified)
{
Logger.Info($"Skipped downloading unmodified resource '{this.From}'");
}
else
{
throw;
}
}
}
}
public class CustomProxyInformation
{
public string ServerAddress { get; }
public NetworkCredential? Credentials { get; }
public CustomProxyInformation(string proxy)
{
if (proxy.Contains("@"))
{
// Extract proxy credentials
int credsFrom = proxy.IndexOf("://") + 3;
int credsTo = proxy.LastIndexOf("@");
string completeCredsStr = proxy.Substring(credsFrom, credsTo - credsFrom);
int credsSeparator = completeCredsStr.IndexOf(":");
string username = completeCredsStr.Substring(0, credsSeparator);
string password = completeCredsStr.Substring(credsSeparator + 1);
this.Credentials = new NetworkCredential(username, password);
this.ServerAddress = proxy.Replace(completeCredsStr + "@", null);
}
else
{
this.ServerAddress = proxy;
}
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,6 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>winsw</RootNamespace>
</PropertyGroup>
<ItemGroup>

View File

@ -1,12 +1,13 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using log4net;
using WinSW.Extensions;
using WinSW.Util;
using static WinSW.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods;
using static WinSW.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.Native;
namespace WinSW.Plugins.RunawayProcessKiller
{
@ -287,5 +288,177 @@ namespace WinSW.Plugins.RunawayProcessKiller
Logger.Error("Cannot update the PID file " + this.Pidfile, ex);
}
}
internal static class Native
{
private const string Kernel32 = "kernel32.dll";
private const string NTDll = "ntdll.dll";
[DllImport(Kernel32)]
internal static extern int IsWow64Process(IntPtr hProcess, out int Wow64Process);
[DllImport(NTDll)]
internal static extern int NtQueryInformationProcess(
IntPtr ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
out PROCESS_BASIC_INFORMATION32 ProcessInformation,
int ProcessInformationLength,
IntPtr ReturnLength = default);
[DllImport(NTDll)]
internal static extern int NtQueryInformationProcess(
IntPtr ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
out PROCESS_BASIC_INFORMATION64 ProcessInformation,
int ProcessInformationLength,
IntPtr ReturnLength = default);
[DllImport(NTDll)]
internal static extern unsafe int NtReadVirtualMemory(
IntPtr ProcessHandle,
IntPtr BaseAddress,
void* Buffer,
IntPtr BufferSize,
IntPtr NumberOfBytesRead = default);
[DllImport(NTDll)]
internal static extern int NtWow64QueryInformationProcess64(
IntPtr ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
out PROCESS_BASIC_INFORMATION64 ProcessInformation,
int ProcessInformationLength,
IntPtr ReturnLength = default);
[DllImport(NTDll)]
internal static extern unsafe int NtWow64ReadVirtualMemory64(
IntPtr ProcessHandle,
long BaseAddress,
void* Buffer,
long BufferSize,
long NumberOfBytesRead = default);
internal enum PROCESSINFOCLASS
{
ProcessBasicInformation = 0,
}
[StructLayout(LayoutKind.Sequential)]
internal readonly struct MEMORY_BASIC_INFORMATION
{
public readonly IntPtr BaseAddress;
private readonly IntPtr AllocationBase;
private readonly uint AllocationProtect;
public readonly IntPtr RegionSize;
private readonly uint State;
private readonly uint Protect;
private readonly uint Type;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PROCESS_BASIC_INFORMATION32
{
private readonly int Reserved1;
public readonly int PebBaseAddress;
private fixed int Reserved2[2];
private readonly uint UniqueProcessId;
private readonly int Reserved3;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PROCESS_BASIC_INFORMATION64
{
private readonly long Reserved1;
public readonly long PebBaseAddress;
private fixed long Reserved2[2];
private readonly ulong UniqueProcessId;
private readonly long Reserved3;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PEB32
{
private fixed byte Reserved1[2];
private readonly byte BeingDebugged;
private fixed byte Reserved2[1];
private fixed int Reserved3[2];
private readonly int Ldr;
public readonly int ProcessParameters;
private fixed int Reserved4[3];
private readonly int AtlThunkSListPtr;
private readonly int Reserved5;
private readonly uint Reserved6;
private readonly int Reserved7;
private readonly uint Reserved8;
private readonly uint AtlThunkSListPtr32;
private fixed int Reserved9[45];
private fixed byte Reserved10[96];
private readonly int PostProcessInitRoutine;
private fixed byte Reserved11[128];
private fixed int Reserved12[1];
private readonly uint SessionId;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PEB64
{
private fixed byte Reserved1[2];
private readonly byte BeingDebugged;
private fixed byte Reserved2[1];
private fixed long Reserved3[2];
private readonly long Ldr;
public readonly long ProcessParameters;
private fixed long Reserved4[3];
private readonly long AtlThunkSListPtr;
private readonly long Reserved5;
private readonly uint Reserved6;
private readonly long Reserved7;
private readonly uint Reserved8;
private readonly uint AtlThunkSListPtr32;
private fixed long Reserved9[45];
private fixed byte Reserved10[96];
private readonly long PostProcessInitRoutine;
private fixed byte Reserved11[128];
private fixed long Reserved12[1];
private readonly uint SessionId;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct RTL_USER_PROCESS_PARAMETERS32
{
private fixed byte Reserved1[16];
private fixed int Reserved2[10];
private readonly UNICODE_STRING32 ImagePathName;
private readonly UNICODE_STRING32 CommandLine;
internal readonly int Environment;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct RTL_USER_PROCESS_PARAMETERS64
{
private fixed byte Reserved1[16];
private fixed long Reserved2[10];
private readonly UNICODE_STRING64 ImagePathName;
private readonly UNICODE_STRING64 CommandLine;
internal readonly long Environment;
}
[StructLayout(LayoutKind.Sequential)]
internal readonly struct UNICODE_STRING32
{
private readonly ushort Length;
private readonly ushort MaximumLength;
private readonly int Buffer;
}
[StructLayout(LayoutKind.Sequential)]
internal readonly struct UNICODE_STRING64
{
private readonly ushort Length;
private readonly ushort MaximumLength;
private readonly long Buffer;
}
}
}
}

View File

@ -1,10 +1,11 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Xml;
using log4net;
using WinSW.Extensions;
using WinSW.Util;
using static WinSW.Plugins.SharedDirectoryMapper.NativeMethods;
using static WinSW.Plugins.SharedDirectoryMapper.SharedDirectoryMapper.Native;
namespace WinSW.Plugins.SharedDirectoryMapper
{
@ -91,5 +92,31 @@ namespace WinSW.Plugins.SharedDirectoryMapper
Win32Exception inner = new Win32Exception(error);
throw new ExtensionException(this.Descriptor.Id, $"{this.DisplayName}: {message} {inner.Message}", inner);
}
internal static class Native
{
internal const uint RESOURCETYPE_DISK = 0x00000001;
private const string MprLibraryName = "mpr.dll";
[DllImport(MprLibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "WNetAddConnection2W")]
internal static extern int WNetAddConnection2(in NETRESOURCE netResource, string? password = null, string? userName = null, uint flags = 0);
[DllImport(MprLibraryName, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "WNetCancelConnection2W")]
internal static extern int WNetCancelConnection2(string name, uint flags = 0, bool force = false);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct NETRESOURCE
{
public uint Scope;
public uint Type;
public uint DisplayType;
public uint Usage;
public string LocalName;
public string RemoteName;
public string Comment;
public string Provider;
}
}
}
}

View File

@ -5,8 +5,6 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>winsw.Plugins.RunawayProcessKiller</RootNamespace>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1'">
@ -14,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\WinSWCore\WinSWCore.csproj" />
<ProjectReference Include="..\WinSW.Core\WinSW.Core.csproj" />
</ItemGroup>
</Project>

View File

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

View File

@ -1,10 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
<RootNamespace>winswTests</RootNamespace>
</PropertyGroup>
<ItemGroup>
@ -26,10 +24,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\ServiceWrapper\winsw.csproj" />
<ProjectReference Include="..\..\Core\WinSWCore\WinSWCore.csproj" />
<ProjectReference Include="..\..\Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj" />
<ProjectReference Include="..\..\Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj" />
<ProjectReference Include="..\WinSW\WinSW.csproj" />
<ProjectReference Include="..\WinSW.Core\WinSW.Core.csproj" />
<ProjectReference Include="..\WinSW.Plugins\WinSW.Plugins.csproj" />
</ItemGroup>
</Project>

48
src/WinSW.sln Normal file
View File

@ -0,0 +1,48 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30128.74
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinSW", "WinSW\WinSW.csproj", "{87BD4253-6957-4D83-B75D-5C4C2F114694}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinSW.Core", "WinSW.Core\WinSW.Core.csproj", "{560E6F6A-18DD-4245-AD1A-D02A9AB9CDD7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinSW.Plugins", "WinSW.Plugins\WinSW.Plugins.csproj", "{B50AD8AA-54BD-4FD9-95A9-8AADC84DD46E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinSW.Tests", "WinSW.Tests\WinSW.Tests.csproj", "{691DE22D-4565-4E3C-9CC7-918A0098219E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4975DCF4-C32C-43ED-A731-8FCC1F7E6746}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{87BD4253-6957-4D83-B75D-5C4C2F114694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{87BD4253-6957-4D83-B75D-5C4C2F114694}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87BD4253-6957-4D83-B75D-5C4C2F114694}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87BD4253-6957-4D83-B75D-5C4C2F114694}.Release|Any CPU.Build.0 = Release|Any CPU
{560E6F6A-18DD-4245-AD1A-D02A9AB9CDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{560E6F6A-18DD-4245-AD1A-D02A9AB9CDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{560E6F6A-18DD-4245-AD1A-D02A9AB9CDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{560E6F6A-18DD-4245-AD1A-D02A9AB9CDD7}.Release|Any CPU.Build.0 = Release|Any CPU
{B50AD8AA-54BD-4FD9-95A9-8AADC84DD46E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B50AD8AA-54BD-4FD9-95A9-8AADC84DD46E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B50AD8AA-54BD-4FD9-95A9-8AADC84DD46E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B50AD8AA-54BD-4FD9-95A9-8AADC84DD46E}.Release|Any CPU.Build.0 = Release|Any CPU
{691DE22D-4565-4E3C-9CC7-918A0098219E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{691DE22D-4565-4E3C-9CC7-918A0098219E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{691DE22D-4565-4E3C-9CC7-918A0098219E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{691DE22D-4565-4E3C-9CC7-918A0098219E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A508CEB3-957E-4B4D-9365-60E5A35EA009}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WinSW.Tests")]

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@ -12,8 +12,6 @@
<Company>CloudBees, Inc.</Company>
<Product>Windows Service Wrapper</Product>
<Copyright>Copyright 2008-2016 Oleg Nenashev, CloudBees, Inc. and other contributors</Copyright>
<RootNamespace>winsw</RootNamespace>
<AssemblyName>WindowsService</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1'">
@ -31,9 +29,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WinSWCore\WinSWCore.csproj" />
<ProjectReference Include="..\..\Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj" />
<ProjectReference Include="..\..\Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj" />
<ProjectReference Include="..\WinSW.Core\WinSW.Core.csproj" />
<ProjectReference Include="..\WinSW.Plugins\WinSW.Plugins.csproj" />
</ItemGroup>
<Target Name="PublishCoreZip" AfterTargets="Publish" Condition="'$(TargetFramework)' == 'netcoreapp3.1' and '$(RuntimeIdentifier)' == ''">
@ -67,9 +64,8 @@
<PropertyGroup>
<InputAssemblies>"$(OutDir)$(TargetFileName)"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)WinSWCore.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)SharedDirectoryMapper.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)RunawayProcessKiller.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)WinSW.Core.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)WinSW.Plugins.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)log4net.dll"</InputAssemblies>
<OutputAssembly>"$(ArtifactsDir)WinSW.$(IdentifierSuffix).exe"</OutputAssembly>
</PropertyGroup>

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

View File

@ -1,91 +0,0 @@

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