diff --git a/.gitignore b/.gitignore
index 52acab8..41c0c8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,6 @@ obj
/winsw.csproj.user
/winsw_cert.pfx
*.user
+/src/packages/NUnit.2.6.4
+/src/packages/ILMerge.MSBuild.Tasks.1.0.0.3
+/winsw_key.snk
diff --git a/src/.nuget/packages.config b/src/.nuget/packages.config
new file mode 100644
index 0000000..e1e92a3
--- /dev/null
+++ b/src/.nuget/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Core/ServiceWrapper/Main.cs b/src/Core/ServiceWrapper/Main.cs
index bf29bbf..dd18771 100644
--- a/src/Core/ServiceWrapper/Main.cs
+++ b/src/Core/ServiceWrapper/Main.cs
@@ -14,13 +14,16 @@ using log4net.Core;
using log4net.Layout;
using log4net.Repository.Hierarchy;
using Microsoft.Win32;
+using winsw.Extensions;
+using winsw.Util;
using WMI;
using ServiceType = WMI.ServiceType;
+using winsw.Native;
using System.Reflection;
namespace winsw
{
- public class WrapperService : ServiceBase, EventLogger
+ public class WrapperService : ServiceBase, EventLogger, IEventWriter
{
private SERVICE_STATUS _wrapperServiceStatus;
@@ -28,6 +31,8 @@ namespace winsw
private readonly ServiceDescriptor _descriptor;
private Dictionary _envs;
+ internal WinSWExtensionManager ExtensionManager { private set; get; }
+
private static readonly ILog Log = LogManager.GetLogger("WinSW");
///
@@ -52,6 +57,7 @@ namespace winsw
{
_descriptor = descriptor;
ServiceName = _descriptor.Id;
+ ExtensionManager = new WinSWExtensionManager(_descriptor);
CanShutdown = true;
CanStop = true;
CanPauseAndContinue = false;
@@ -246,6 +252,22 @@ namespace winsw
LogEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
WriteEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
+ // Load and start extensions
+ ExtensionManager.LoadExtensions(this);
+ try
+ {
+ ExtensionManager.OnStart(this);
+ }
+ catch (ExtensionException ex)
+ {
+ LogEvent("Failed to start extension " + ex.ExtensionId + "\n" + ex.Message, EventLogEntryType.Error);
+ WriteEvent("Failed to start extension " + ex.ExtensionId, ex);
+ //TODO: Exit on error?
+ }
+
+ LogEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
+ WriteEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
+
StartProcess(_process, startarguments, _descriptor.Executable);
// send stdout and stderr to its respective output file.
@@ -327,6 +349,17 @@ namespace winsw
SignalShutdownComplete();
}
+ // Stop extensions
+ try
+ {
+ ExtensionManager.OnStop(this);
+ }
+ catch (ExtensionException ex)
+ {
+ LogEvent("Failed to stop extension " + ex.ExtensionId + "\n" + ex.Message, EventLogEntryType.Error);
+ WriteEvent("Failed to stop extension " + ex.ExtensionId, ex);
+ }
+
if (_systemShuttingdown && _descriptor.BeepOnShutdown)
{
Console.Beep();
diff --git a/src/Core/ServiceWrapper/packages.config b/src/Core/ServiceWrapper/packages.config
index 7a2c004..682cb90 100644
--- a/src/Core/ServiceWrapper/packages.config
+++ b/src/Core/ServiceWrapper/packages.config
@@ -1,4 +1,6 @@
+
+
\ No newline at end of file
diff --git a/src/Core/ServiceWrapper/winsw.csproj b/src/Core/ServiceWrapper/winsw.csproj
index 0844ff4..c13d484 100644
--- a/src/Core/ServiceWrapper/winsw.csproj
+++ b/src/Core/ServiceWrapper/winsw.csproj
@@ -9,14 +9,14 @@
Exe
Properties
winsw
- winsw
+ WindowsService
v2.0
512
false
true
- ..\..\..\winsw_cert.pfx
+ $(SolutionDir)..\winsw_key.snk
@@ -74,20 +74,11 @@
-
-
-
-
-
Component
-
-
-
-
@@ -100,7 +91,10 @@
-
+
+ Designer
+
+
@@ -114,6 +108,17 @@
true
+
+
+ {ca5c71db-c5a8-4c27-bf83-8e6daed9d6b5}
+ SharedDirectoryMapper
+
+
+ {9d0c63e2-b6ff-4a85-bd36-b3e5d7f27d06}
+ WinSWCore
+
+
+
-
-
-
-
@@ -133,4 +134,46 @@
+
+
+
+
+
+
+
+
+
+
+ $(ProjectDir)$(OutDir)winsw.exe
+ $(AssemblyOriginatorKeyFile)
+
+
+
+ $(OutputPath)sn-path.txt
+
+
+
+
+
+
+
+
+
+
+
+ $([System.Text.RegularExpressions.Regex]::Replace('$(SNPath)', ';.*', ''))
+
+
+
+
+
+ $(SolutionDir)packages\ilmerge.2.14.1208\tools
+ $(OutputPath)winsw_cert.pub
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Core/ServiceWrapper/Download.cs b/src/Core/WinSWCore/Download.cs
similarity index 100%
rename from src/Core/ServiceWrapper/Download.cs
rename to src/Core/WinSWCore/Download.cs
diff --git a/src/Core/ServiceWrapper/DynamicProxy.cs b/src/Core/WinSWCore/DynamicProxy.cs
similarity index 100%
rename from src/Core/ServiceWrapper/DynamicProxy.cs
rename to src/Core/WinSWCore/DynamicProxy.cs
diff --git a/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs b/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs
new file mode 100644
index 0000000..acba93c
--- /dev/null
+++ b/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Xml;
+using winsw.Util;
+
+namespace winsw.Extensions
+{
+ public abstract class AbstractWinSWExtension : IWinSWExtension
+ {
+ public abstract String DisplayName { get; }
+ public WinSWExtensionDescriptor Descriptor { get; set; }
+
+ public virtual void Configure(ServiceDescriptor descriptor, XmlNode node, IEventWriter logger)
+ {
+ // Do nothing
+ }
+
+ public virtual void OnStart(IEventWriter eventWriter)
+ {
+ // Do nothing
+ }
+
+ public virtual void OnStop(IEventWriter eventWriter)
+ {
+ // Do nothing
+ }
+ }
+}
diff --git a/src/Core/WinSWCore/Extensions/ExtensionException.cs b/src/Core/WinSWCore/Extensions/ExtensionException.cs
new file mode 100644
index 0000000..5ac682a
--- /dev/null
+++ b/src/Core/WinSWCore/Extensions/ExtensionException.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace winsw.Extensions
+{
+ public class ExtensionException : WinSWException
+ {
+ public String ExtensionId { get; private set; }
+
+ public ExtensionException(String extensionName, String message)
+ : base(message)
+ {
+ ExtensionId = extensionName;
+ }
+
+ public ExtensionException(String extensionName, String message, Exception innerException)
+ : base(message, innerException)
+ {
+ ExtensionId = extensionName;
+ }
+
+ public override string Message
+ {
+ get
+ {
+ return ExtensionId + ": " + base.Message;
+ }
+ }
+ }
+}
diff --git a/src/Core/WinSWCore/Extensions/ExtensionPointAttribute.cs b/src/Core/WinSWCore/Extensions/ExtensionPointAttribute.cs
new file mode 100644
index 0000000..61a4e11
--- /dev/null
+++ b/src/Core/WinSWCore/Extensions/ExtensionPointAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace winsw.Extensions
+{
+ ///
+ /// This attribute is used to identify extension points within the code
+ ///
+ ///
+ /// Each extension point implements its own entry type.
+ ///
+ class ExtensionPointAttribute
+ {
+ }
+}
diff --git a/src/Core/WinSWCore/Extensions/IWinSWExtension.cs b/src/Core/WinSWCore/Extensions/IWinSWExtension.cs
new file mode 100644
index 0000000..c114381
--- /dev/null
+++ b/src/Core/WinSWCore/Extensions/IWinSWExtension.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Xml;
+using winsw.Util;
+
+namespace winsw.Extensions
+{
+ ///
+ /// Interface for Win Service Wrapper Extension
+ ///
+ ///
+ /// All implementations should provide the default empty constructor.
+ /// The initialization will be performed by Init methods
+ ///
+ public interface IWinSWExtension
+ {
+ ///
+ /// Extension name to be displayed in logs
+ ///
+ String DisplayName { get; }
+
+ ///
+ /// Extension descriptor
+ ///
+ WinSWExtensionDescriptor Descriptor { get; set; }
+
+ ///
+ /// Init handler. Extension should load it's config during that step
+ ///
+ /// Service descriptor
+ /// Configuration node
+ void Configure(ServiceDescriptor descriptor, XmlNode node, IEventWriter logger);
+
+ ///
+ /// Start handler. Called during startup of the service before the child process.
+ ///
+ /// Logger
+ /// Any error during execution
+ void OnStart(IEventWriter logger);
+
+ ///
+ /// Stop handler. Called during stop of the service
+ ///
+ /// Logger
+ /// Any error during execution
+ void OnStop(IEventWriter logger);
+ }
+}
diff --git a/src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs b/src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs
new file mode 100644
index 0000000..3d58c73
--- /dev/null
+++ b/src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Xml;
+using winsw.Util;
+
+namespace winsw.Extensions
+{
+ ///
+ /// Describes WinSW extensions in
+ ///
+ ///
+ /// Any extension has its own descriptor instance.
+ ///
+ public class WinSWExtensionDescriptor
+ {
+ ///
+ /// Unique extension ID
+ ///
+ public String Id { get; private set; }
+
+ ///
+ /// Exception is enabled
+ ///
+ public bool Enabled { get; private set; }
+
+ ///
+ /// Extension classname
+ ///
+ public String ClassName { get; private set; }
+
+ private WinSWExtensionDescriptor(string id, string className, bool enabled)
+ {
+ Id = id;
+ Enabled = enabled;
+ ClassName = className;
+ }
+
+ public static WinSWExtensionDescriptor FromXml(XmlElement node)
+ {
+ bool enabled = XmlHelper.SingleAttribute(node, "enabled", true);
+ string className = XmlHelper.SingleAttribute(node, "className");
+ string id = XmlHelper.SingleAttribute(node, "id");
+ return new WinSWExtensionDescriptor(id, className, enabled);
+ }
+ }
+}
diff --git a/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs b/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs
new file mode 100644
index 0000000..c305409
--- /dev/null
+++ b/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using System.Reflection;
+using System.Diagnostics;
+using winsw.Util;
+
+namespace winsw.Extensions
+{
+ public class WinSWExtensionManager
+ {
+ public Dictionary Extensions { private set; get; }
+ public ServiceDescriptor ServiceDescriptor { private set; get; }
+
+ public WinSWExtensionManager(ServiceDescriptor serviceDescriptor)
+ {
+ ServiceDescriptor = serviceDescriptor;
+ Extensions = new Dictionary();
+ }
+
+ ///
+ /// Starts all extensions
+ ///
+ /// Start failure
+ public void OnStart(IEventWriter logger)
+ {
+ foreach (var ext in Extensions)
+ {
+ ext.Value.OnStart(logger);
+ }
+ }
+
+ ///
+ /// Stops all extensions
+ ///
+ /// Stop failure
+ public void OnStop(IEventWriter logger)
+ {
+ foreach (var ext in Extensions)
+ {
+ ext.Value.OnStop(logger);
+ }
+ }
+
+ //TODO: Implement loading of external extensions. Current version supports internal hack
+ #region Extension load management
+
+ public void LoadExtensions(IEventWriter logger)
+ {
+ var extensionIds = ServiceDescriptor.ExtensionIds;
+ foreach (String extensionId in extensionIds)
+ {
+ LoadExtension(extensionId, logger);
+ }
+ }
+
+ ///
+ /// Loads extensions from the configuration file
+ ///
+ /// Extension ID
+ /// Logger
+ /// Loading failure
+ private void LoadExtension(string id, IEventWriter logger)
+ {
+ if (Extensions.ContainsKey(id))
+ {
+ throw new ExtensionException(id, "Extension has been already loaded");
+ }
+
+ var extensionsConfig = ServiceDescriptor.ExtensionsConfiguration;
+ XmlElement configNode =(extensionsConfig != null) ? extensionsConfig.SelectSingleNode("extension[@id='"+id+"'][1]") as XmlElement : null;
+ if (configNode == null)
+ {
+ throw new ExtensionException(id, "Cannot get the configuration entry");
+ }
+
+ var descriptor = WinSWExtensionDescriptor.FromXml(configNode);
+ if (descriptor.Enabled)
+ {
+ IWinSWExtension extension = CreateExtensionInstance(descriptor.Id, descriptor.ClassName);
+ extension.Descriptor = descriptor;
+ extension.Configure(ServiceDescriptor, configNode, logger);
+ Extensions.Add(id, extension);
+ logger.LogEvent("Extension loaded: "+id, EventLogEntryType.Information);
+ }
+ else
+ {
+ logger.LogEvent("Extension is disabled: " + id, EventLogEntryType.Warning);
+ }
+
+ }
+
+ private IWinSWExtension CreateExtensionInstance(string id, string className)
+ {
+ ActivationContext ac = AppDomain.CurrentDomain.ActivationContext;
+ Assembly assembly = Assembly.GetCallingAssembly();
+ Object created;
+
+ try
+ {
+ Type t = Type.GetType(className);
+ if (t == null)
+ {
+ throw new ExtensionException(id, "Class "+className+" does not exist");
+ }
+ created = Activator.CreateInstance(t);
+ }
+ catch (Exception ex)
+ {
+ throw new ExtensionException(id, "Cannot load the class by name: "+className, ex);
+ }
+
+ var extension = created as IWinSWExtension;
+ if (extension == null)
+ {
+ throw new ExtensionException(id, "The loaded class is not a WinSW extension: " + className + ". Type is " + created.GetType());
+ }
+ return extension;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Core/ServiceWrapper/LogAppenders.cs b/src/Core/WinSWCore/LogAppenders.cs
similarity index 100%
rename from src/Core/ServiceWrapper/LogAppenders.cs
rename to src/Core/WinSWCore/LogAppenders.cs
diff --git a/src/Core/ServiceWrapper/Advapi32.cs b/src/Core/WinSWCore/Native/Advapi32.cs
similarity index 96%
rename from src/Core/ServiceWrapper/Advapi32.cs
rename to src/Core/WinSWCore/Native/Advapi32.cs
index ae6ea7a..cc2ed32 100755
--- a/src/Core/ServiceWrapper/Advapi32.cs
+++ b/src/Core/WinSWCore/Native/Advapi32.cs
@@ -6,9 +6,9 @@ using System.Text;
// ReSharper disable InconsistentNaming
-namespace winsw
+namespace winsw.Native
{
- class ServiceManager : IDisposable
+ public class ServiceManager : IDisposable
{
private IntPtr _handle;
@@ -39,7 +39,7 @@ namespace winsw
}
}
- class Service : IDisposable
+ public class Service : IDisposable
{
internal IntPtr Handle;
@@ -87,7 +87,7 @@ namespace winsw
}
}
- static class LogonAsAService
+ public static class LogonAsAService
{
public static void AddLogonAsAServiceRight(string username)
{
@@ -251,7 +251,7 @@ namespace winsw
/// Advapi32.dll wrapper for performing additional service related operations that are not
/// available in WMI.
///
- internal class Advapi32
+ public class Advapi32
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
@@ -272,7 +272,7 @@ namespace winsw
internal static extern bool CloseServiceHandle(IntPtr hSCObject);
[DllImport("advapi32.DLL")]
- internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
+ public static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
[DllImport("advapi32.dll", PreserveSig = true)]
internal static extern UInt32 LsaOpenPolicy(ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, Int32 DesiredAccess,
diff --git a/src/Core/ServiceWrapper/Kernel32.cs b/src/Core/WinSWCore/Native/Kernel32.cs
similarity index 79%
rename from src/Core/ServiceWrapper/Kernel32.cs
rename to src/Core/WinSWCore/Native/Kernel32.cs
index 29b20ae..3d57a9a 100755
--- a/src/Core/ServiceWrapper/Kernel32.cs
+++ b/src/Core/WinSWCore/Native/Kernel32.cs
@@ -1,18 +1,18 @@
using System;
using System.Runtime.InteropServices;
-namespace winsw
+namespace winsw.Native
{
///
/// kernel32.dll P/Invoke wrappers
///
- internal class Kernel32
+ public class Kernel32
{
[DllImport("Kernel32.dll", SetLastError = true)]
- internal static extern int SetStdHandle(int device, IntPtr handle);
+ public static extern int SetStdHandle(int device, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
- internal static extern bool CreateProcess(string lpApplicationName,
+ public static extern bool CreateProcess(string lpApplicationName,
string lpCommandLine, IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes, bool bInheritHandles,
uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
@@ -20,11 +20,11 @@ namespace winsw
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
- internal static extern int GetLastError();
+ public static extern int GetLastError();
}
[StructLayout(LayoutKind.Sequential)]
- internal struct PROCESS_INFORMATION
+ public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
@@ -33,7 +33,7 @@ namespace winsw
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
- struct STARTUPINFO
+ public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
diff --git a/src/Core/ServiceWrapper/PeriodicRollingCalendar.cs b/src/Core/WinSWCore/PeriodicRollingCalendar.cs
similarity index 100%
rename from src/Core/ServiceWrapper/PeriodicRollingCalendar.cs
rename to src/Core/WinSWCore/PeriodicRollingCalendar.cs
diff --git a/src/Core/WinSWCore/Properties/AssemblyInfo.cs b/src/Core/WinSWCore/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..5cb6f2a
--- /dev/null
+++ b/src/Core/WinSWCore/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("WinSWCore")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("WinSWCore")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("8f845354-ba20-455d-82d1-9b6ec4e0e517")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Core/ServiceWrapper/ServiceDescriptor.cs b/src/Core/WinSWCore/ServiceDescriptor.cs
similarity index 92%
rename from src/Core/ServiceWrapper/ServiceDescriptor.cs
rename to src/Core/WinSWCore/ServiceDescriptor.cs
index 1263087..8d770fe 100755
--- a/src/Core/ServiceWrapper/ServiceDescriptor.cs
+++ b/src/Core/WinSWCore/ServiceDescriptor.cs
@@ -5,6 +5,8 @@ using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Xml;
+using winsw.Native;
+using winsw.Util;
using WMI;
namespace winsw
@@ -227,9 +229,7 @@ namespace winsw
}
}
- ///
- /// Optional working directory.
- ///
+
public string WorkingDirectory {
get {
var wd = SingleElement("workingdirectory", true);
@@ -237,6 +237,37 @@ namespace winsw
}
}
+ public List ExtensionIds
+ {
+ get
+ {
+ List res = new List();
+
+ XmlNode argumentNode = ExtensionsConfiguration;
+ XmlNodeList extensions = argumentNode != null ? argumentNode.SelectNodes("extension") : null;
+ if ( extensions != null)
+ {
+ foreach (XmlNode e in extensions)
+ {
+ XmlElement extension = (XmlElement)e;
+ String extensionId = XmlHelper.SingleAttribute(extension, "id");
+ res.Add(extensionId);
+ }
+ }
+
+ return res;
+ }
+ }
+
+ public XmlNode ExtensionsConfiguration
+ {
+ get
+ {
+ XmlNode argumentNode = dom.SelectSingleNode("//extensions");
+ return argumentNode;
+ }
+ }
+
///
/// Combines the contents of all the elements of the given name,
/// or return null if no element exists. Handles whitespace quotation.
diff --git a/src/Core/WinSWCore/Util/IEventWriter.cs b/src/Core/WinSWCore/Util/IEventWriter.cs
new file mode 100644
index 0000000..7f49dfa
--- /dev/null
+++ b/src/Core/WinSWCore/Util/IEventWriter.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Diagnostics;
+
+namespace winsw.Util
+{
+ public interface IEventWriter
+ {
+ void LogEvent(String message);
+ void LogEvent(String message, EventLogEntryType type);
+ }
+}
diff --git a/src/Core/WinSWCore/Util/XmlHelper.cs b/src/Core/WinSWCore/Util/XmlHelper.cs
new file mode 100644
index 0000000..687a8d2
--- /dev/null
+++ b/src/Core/WinSWCore/Util/XmlHelper.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+using System.IO;
+
+namespace winsw.Util
+{
+ public class XmlHelper
+ {
+ ///
+ /// Retrieves a single string element
+ ///
+ /// Parent node
+ /// Element name
+ /// If otional, don't throw an exception if the elemen is missing
+ /// String value or null
+ /// The required element is missing
+ public static string SingleElement(XmlNode node, string tagName, Boolean optional)
+ {
+ var n = node.SelectSingleNode(tagName);
+ if (n == null && !optional) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
+ return n == null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
+ }
+
+ ///
+ /// Retrieves a single node
+ ///
+ /// Parent node
+ /// Element name
+ /// If otional, don't throw an exception if the elemen is missing
+ /// String value or null
+ /// The required element is missing
+ public static XmlNode SingleNode(XmlNode node, string tagName, Boolean optional)
+ {
+ var n = node.SelectSingleNode(tagName);
+ if (n == null && !optional) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
+ return n;
+ }
+
+ ///
+ /// Retrieves a single mandatory attribute
+ ///
+ /// Parent node
+ /// Attribute name
+ /// Attribute value
+ /// The required attribute is missing
+ public static TAttributeType SingleAttribute (XmlElement node, string attributeName)
+ {
+ if (!node.HasAttribute(attributeName))
+ {
+ throw new InvalidDataException("Attribute <" + attributeName + "> is missing in configuration XML");
+ }
+
+ return SingleAttribute(node, attributeName, default(TAttributeType));
+ }
+
+ ///
+ /// Retrieves a single optional attribute
+ ///
+ /// Parent node
+ /// Attribute name
+ /// Default value
+ /// Attribute value (or default)
+ public static TAttributeType SingleAttribute(XmlElement node, string attributeName, TAttributeType defaultValue)
+ {
+ if (!node.HasAttribute(attributeName)) return defaultValue;
+
+ string rawValue = node.GetAttribute(attributeName);
+ string substitutedValue = Environment.ExpandEnvironmentVariables(rawValue);
+ var value = (TAttributeType)Convert.ChangeType(substitutedValue, typeof(TAttributeType));
+ return value;
+ }
+ }
+}
diff --git a/src/Core/WinSWCore/WinSWCore.csproj b/src/Core/WinSWCore/WinSWCore.csproj
new file mode 100644
index 0000000..6ea87bb
--- /dev/null
+++ b/src/Core/WinSWCore/WinSWCore.csproj
@@ -0,0 +1,89 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}
+ Library
+ Properties
+ winsw
+ WinSWCore
+ v2.0
+ 512
+ ..\..\
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+
+
+ ..\..\..\winsw_cert.pfx
+
+
+
+ ..\..\packages\log4net.2.0.3\lib\net20-full\log4net.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Core/WinSWCore/WinSWException.cs b/src/Core/WinSWCore/WinSWException.cs
new file mode 100644
index 0000000..870660e
--- /dev/null
+++ b/src/Core/WinSWCore/WinSWException.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace winsw
+{
+ public class WinSWException : Exception
+ {
+ public WinSWException(String message)
+ : base(message)
+ { }
+
+ public WinSWException(String message, Exception innerException)
+ : base(message, innerException)
+ { }
+ }
+}
diff --git a/src/Core/ServiceWrapper/Wmi.cs b/src/Core/WinSWCore/Wmi.cs
similarity index 100%
rename from src/Core/ServiceWrapper/Wmi.cs
rename to src/Core/WinSWCore/Wmi.cs
diff --git a/src/Core/ServiceWrapper/WmiSchema.cs b/src/Core/WinSWCore/WmiSchema.cs
similarity index 100%
rename from src/Core/ServiceWrapper/WmiSchema.cs
rename to src/Core/WinSWCore/WmiSchema.cs
diff --git a/src/Core/WinSWCore/packages.config b/src/Core/WinSWCore/packages.config
new file mode 100644
index 0000000..7a2c004
--- /dev/null
+++ b/src/Core/WinSWCore/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Plugins/SharedDirectoryMapper/Properties/AssemblyInfo.cs b/src/Plugins/SharedDirectoryMapper/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..5d8fa29
--- /dev/null
+++ b/src/Plugins/SharedDirectoryMapper/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SharedDirectoryMapper")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SharedDirectoryMapper")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d962c792-b900-4e60-8ae6-6c8d05b23a61")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs
new file mode 100644
index 0000000..97b36a9
--- /dev/null
+++ b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using System.Diagnostics;
+using winsw.Extensions;
+using winsw.Util;
+
+namespace winsw.Plugins.SharedDirectoryMapper
+{
+ public class SharedDirectoryMapper : AbstractWinSWExtension
+ {
+ private readonly SharedDirectoryMappingHelper _mapper = new SharedDirectoryMappingHelper();
+ private readonly List _entries = new List();
+
+ public override String DisplayName { get { return "Shared Directory Mapper"; } }
+
+ public SharedDirectoryMapper()
+ {
+ }
+
+ public SharedDirectoryMapper(bool enableMapping, string directoryUNC, string driveLabel)
+ {
+ SharedDirectoryMapperConfig config = new SharedDirectoryMapperConfig(enableMapping, driveLabel, directoryUNC);
+ _entries.Add(config);
+ }
+
+ public override void Configure(ServiceDescriptor descriptor, XmlNode node, IEventWriter logger)
+ {
+ var nodes = XmlHelper.SingleNode(node, "mapping", false).SelectNodes("map");
+ if (nodes != null)
+ {
+ foreach (XmlNode mapNode in nodes)
+ {
+ var mapElement = mapNode as XmlElement;
+ if (mapElement != null)
+ {
+ var config = SharedDirectoryMapperConfig.FromXml(mapElement);
+ _entries.Add(config);
+ }
+ }
+ }
+ }
+
+ public override void OnStart(IEventWriter eventWriter)
+ {
+ foreach (SharedDirectoryMapperConfig config in _entries)
+ {
+ if (config.EnableMapping)
+ {
+ eventWriter.LogEvent(DisplayName + ": Mapping shared directory " + config.UNCPath + " to " + config.Label, EventLogEntryType.Information);
+ try
+ {
+ _mapper.MapDirectory(config.Label, config.UNCPath);
+ }
+ catch (MapperException ex)
+ {
+ HandleMappingError(config, eventWriter, ex);
+ }
+ }
+ else
+ {
+ eventWriter.LogEvent(DisplayName + ": Mapping of " + config.Label + " is disabled", EventLogEntryType.Warning);
+ }
+ }
+ }
+
+ public override void OnStop(IEventWriter eventWriter)
+ {
+ foreach (SharedDirectoryMapperConfig config in _entries)
+ {
+ if (config.EnableMapping)
+ {
+ try
+ {
+ _mapper.UnmapDirectory(config.Label);
+ }
+ catch (MapperException ex)
+ {
+ HandleMappingError(config, eventWriter, ex);
+ }
+ }
+ }
+ }
+
+ private void HandleMappingError(SharedDirectoryMapperConfig config, IEventWriter eventWriter, MapperException ex) {
+ String prefix = "Mapping of " + config.Label+ " ";
+ eventWriter.LogEvent(prefix + "STDOUT: " + ex.Process.StandardOutput.ReadToEnd(), EventLogEntryType.Information);
+ eventWriter.LogEvent(prefix + "STDERR: " + ex.Process.StandardError.ReadToEnd(), EventLogEntryType.Information);
+
+ throw new ExtensionException(Descriptor.Id, DisplayName + ": " + prefix + "failed", ex);
+ }
+ }
+}
diff --git a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj
new file mode 100644
index 0000000..6552879
--- /dev/null
+++ b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj
@@ -0,0 +1,57 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}
+ Library
+ Properties
+ winsw.Plugins.SharedDirectoryMapper
+ SharedDirectoryMapper
+ v2.0
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {9d0c63e2-b6ff-4a85-bd36-b3e5d7f27d06}
+ WinSWCore
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperConfig.cs b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperConfig.cs
new file mode 100644
index 0000000..59276e8
--- /dev/null
+++ b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperConfig.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Xml;
+using winsw.Util;
+
+namespace winsw.Plugins.SharedDirectoryMapper
+{
+ ///
+ /// Stores configuration entries for SharedDirectoryMapper extension.
+ ///
+ public class SharedDirectoryMapperConfig
+ {
+ public bool EnableMapping { get; set; }
+ public String Label { get; set; }
+ public String UNCPath { get; set; }
+
+ public SharedDirectoryMapperConfig(bool enableMapping, string label, string uncPath)
+ {
+ EnableMapping = enableMapping;
+ Label = label;
+ UNCPath = uncPath;
+ }
+
+ public static SharedDirectoryMapperConfig FromXml(XmlElement node)
+ {
+ bool enableMapping = XmlHelper.SingleAttribute(node, "enabled", true);
+ string label = XmlHelper.SingleAttribute(node, "label");
+ string uncPath = XmlHelper.SingleAttribute(node, "uncpath");
+ return new SharedDirectoryMapperConfig(enableMapping, label, uncPath);
+ }
+ }
+}
diff --git a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperHelper.cs b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperHelper.cs
new file mode 100644
index 0000000..8016a93
--- /dev/null
+++ b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperHelper.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Diagnostics;
+using winsw.Util;
+
+namespace winsw.Plugins.SharedDirectoryMapper
+{
+ class SharedDirectoryMappingHelper
+ {
+ ///
+ /// Invokes a system command
+ ///
+ ///
+ /// Command to be executed
+ /// Command arguments
+ /// Operation failure
+ private void InvokeCommand(String command, String args)
+ {
+ Process p = new Process
+ {
+ StartInfo =
+ {
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ FileName = command,
+ Arguments = args
+ }
+ };
+
+ p.Start();
+ p.WaitForExit();
+ if (p.ExitCode != 0)
+ {
+ throw new MapperException(p, command, args);
+ }
+ }
+
+ ///
+ /// Maps the remote directory
+ ///
+ /// Disk label
+ /// UNC path to the directory
+ /// Operation failure
+ public void MapDirectory(String label, String uncPath)
+ {
+ InvokeCommand("net.exe", " use " + label + " " + uncPath);
+ }
+
+ ///
+ /// Unmaps the label
+ ///
+ /// Disk label
+ /// Operation failure
+ public void UnmapDirectory(String label)
+ {
+ InvokeCommand("net.exe", " use /DELETE /YES " + label);
+ }
+ }
+
+ class MapperException : WinSWException
+ {
+ public String Call { get; private set; }
+ public Process Process { get; private set; }
+
+ public MapperException(Process process, string command, string args)
+ : base("Command " + command + " " + args + " failed with code " + process.ExitCode)
+ {
+ Call = command + " " + args;
+ Process = process;
+ }
+ }
+}
diff --git a/src/Plugins/SharedDirectoryMapper/sampleConfig.xml b/src/Plugins/SharedDirectoryMapper/sampleConfig.xml
new file mode 100644
index 0000000..8cf5907
--- /dev/null
+++ b/src/Plugins/SharedDirectoryMapper/sampleConfig.xml
@@ -0,0 +1,18 @@
+
+
+ SERVICE_NAME
+ Jenkins Slave
+ This service runs a slave for Jenkins continuous integration system.
+ C:\Program Files\Java\jre7\bin\java.exe
+ -Xrs -jar "%BASE%\slave.jar" -jnlpUrl ...
+ rotate
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Test/winswTests/Extensions/WinSWExtensionManagerTest.cs b/src/Test/winswTests/Extensions/WinSWExtensionManagerTest.cs
new file mode 100644
index 0000000..5ccd638
--- /dev/null
+++ b/src/Test/winswTests/Extensions/WinSWExtensionManagerTest.cs
@@ -0,0 +1,62 @@
+using winsw;
+using NUnit.Framework;
+using winsw.Extensions;
+using winsw.Plugins.SharedDirectoryMapper;
+using winswTests.util;
+
+namespace winswTests.extensions
+{
+ [TestFixture]
+ class WinSWExtensionManagerTest
+ {
+ ServiceDescriptor _testServiceDescriptor;
+ readonly TestLogger _logger = new TestLogger();
+
+ [SetUp]
+ public void SetUp()
+ {
+ string testExtension = typeof (SharedDirectoryMapper).ToString();
+ string seedXml = ""
+ + " "
+ + " SERVICE_NAME "
+ + " Jenkins Slave "
+ + " This service runs a slave for Jenkins continuous integration system. "
+ + " C:\\Program Files\\Java\\jre7\\bin\\java.exe "
+ + " -Xrs -jar \\\"%BASE%\\slave.jar\\\" -jnlpUrl ... "
+ + " rotate "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "";
+ _testServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
+ }
+
+ [Test]
+ public void LoadExtensions()
+ {
+ WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
+ manager.LoadExtensions(_logger);
+ Assert.AreEqual(2, manager.Extensions.Count, "Two extensions should be loaded");
+ }
+
+ [Test]
+ public void StartStopExtension()
+ {
+ WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor);
+ manager.LoadExtensions(_logger);
+ manager.OnStart(_logger);
+ manager.OnStop(_logger);
+ }
+ }
+}
diff --git a/src/Test/winswTests/NunitTest.nunit b/src/Test/winswTests/NunitTest.nunit
new file mode 100644
index 0000000..3eaf587
--- /dev/null
+++ b/src/Test/winswTests/NunitTest.nunit
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Test/winswTests/Util/TestLogger.cs b/src/Test/winswTests/Util/TestLogger.cs
new file mode 100644
index 0000000..d73ffab
--- /dev/null
+++ b/src/Test/winswTests/Util/TestLogger.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Diagnostics;
+using winsw.Util;
+
+namespace winswTests.util
+{
+ class TestLogger : IEventWriter
+ {
+ public void LogEvent(String message)
+ {
+ Console.WriteLine(message);
+ }
+
+ public void LogEvent(String message, EventLogEntryType type)
+ {
+ Console.WriteLine("[" + type + "]" + message);
+ }
+ }
+}
diff --git a/src/Test/winswTests/winswTests.csproj b/src/Test/winswTests/winswTests.csproj
index 83bddf7..4818a3c 100644
--- a/src/Test/winswTests/winswTests.csproj
+++ b/src/Test/winswTests/winswTests.csproj
@@ -52,9 +52,11 @@
+
+
@@ -62,10 +64,19 @@
{0DE77F55-ADE5-43C1-999A-0BC81153B039}
winsw
+
+ {9d0c63e2-b6ff-4a85-bd36-b3e5d7f27d06}
+ WinSWCore
+
+
+ {ca5c71db-c5a8-4c27-bf83-8e6daed9d6b5}
+ SharedDirectoryMapper
+
+
diff --git a/src/packages/repositories.config b/src/packages/repositories.config
index 486e754..d72f1c5 100644
--- a/src/packages/repositories.config
+++ b/src/packages/repositories.config
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/src/winsw.sln b/src/winsw.sln
index 15d09bd..95098b0 100644
--- a/src/winsw.sln
+++ b/src/winsw.sln
@@ -1,4 +1,3 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
@@ -11,25 +10,91 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6BDF40
ProjectSection(SolutionItems) = preProject
.nuget\NuGet.Config = .nuget\NuGet.Config
.nuget\NuGet.exe = .nuget\NuGet.exe
- .nuget\NuGet.targets = .nuget\NuGet.targets
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{077C2CEC-B687-4B53-86E9-C1A1BF5554E5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{BC4AD891-E87E-4F30-867C-FD8084A29E5D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5297623A-1A95-4F89-9AAE-DA634081EC86}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{D8806424-4640-440C-952D-37790B603C27}"
+ ProjectSection(SolutionItems) = preProject
+ Build.proj = Build.proj
+ .build\MSBuild.Community.Tasks.dll = .build\MSBuild.Community.Tasks.dll
+ .build\MSBuild.Community.Tasks.targets = .build\MSBuild.Community.Tasks.targets
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|Win32 = Debug|Win32
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
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|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Mixed Platforms.Build.0 = Release|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|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Mixed Platforms.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|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Mixed Platforms.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|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Mixed Platforms.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|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Mixed Platforms.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|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Mixed Platforms.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|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Mixed Platforms.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|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Mixed Platforms.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
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}
+ EndGlobalSection
EndGlobal
diff --git a/src/winsw.sln.DotSettings b/src/winsw.sln.DotSettings
new file mode 100644
index 0000000..45d451a
--- /dev/null
+++ b/src/winsw.sln.DotSettings
@@ -0,0 +1,3 @@
+
+ SW
+ UNC
\ No newline at end of file