Browse Source

Integrate changes in WinSW core and SharedDirectoryMapper from #42

The code is compilable, the SharedDirectoryMapper won't be included into WinSW

Signed-off-by: Oleg Nenashev <[email protected]>

Conflicts:
	src/Core/ServiceWrapper/Main.cs

Conflicts:
	src/Core/ServiceWrapper/ServiceDescriptor.cs
	src/Test/winswTests/winswTests.csproj
	src/winsw.sln
Oleg Nenashev 10 years ago
parent
commit
2546168ed3
26 changed files with 995 additions and 4 deletions
  1. 27 0
      src/Core/ServiceWrapper/Extensions/AbstractWinSWExtension.cs
  2. 30 0
      src/Core/ServiceWrapper/Extensions/ExtensionException.cs
  3. 44 0
      src/Core/ServiceWrapper/Extensions/IWinSWExtension.cs
  4. 39 0
      src/Core/ServiceWrapper/Extensions/WinSWExtensionDescriptor.cs
  5. 123 0
      src/Core/ServiceWrapper/Extensions/WinSWExtensionManager.cs
  6. 13 0
      src/Core/ServiceWrapper/IEventWriter.cs
  7. 33 1
      src/Core/ServiceWrapper/Main.cs
  8. 33 3
      src/Core/ServiceWrapper/ServiceDescriptor.cs
  9. 13 0
      src/Core/ServiceWrapper/Util/IEventWriter.cs
  10. 15 0
      src/Core/ServiceWrapper/Util/WinSWException.cs
  11. 73 0
      src/Core/ServiceWrapper/Util/XmlHelper.cs
  12. 17 0
      src/Core/ServiceWrapper/WinSWException.cs
  13. 75 0
      src/Core/ServiceWrapper/XmlHelper.cs
  14. 9 0
      src/Core/ServiceWrapper/winsw.csproj
  15. 36 0
      src/Plugins/SharedDirectoryMapper/Properties/AssemblyInfo.cs
  16. 93 0
      src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs
  17. 57 0
      src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj
  18. 31 0
      src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperConfig.cs
  19. 73 0
      src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperHelper.cs
  20. 18 0
      src/Plugins/SharedDirectoryMapper/sampleConfig.xml
  21. 62 0
      src/Test/winswTests/Extensions/WinSWExtensionManagerTest.cs
  22. 7 0
      src/Test/winswTests/NunitTest.nunit
  23. 19 0
      src/Test/winswTests/Util/TestLogger.cs
  24. 7 0
      src/Test/winswTests/winswTests.csproj
  25. 45 0
      src/winsw.sln
  26. 3 0
      src/winsw.sln.DotSettings

+ 27 - 0
src/Core/ServiceWrapper/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
+        }
+    }
+}

+ 30 - 0
src/Core/ServiceWrapper/Extensions/ExtensionException.cs

@@ -0,0 +1,30 @@
+using System;
+using winsw.Util;
+
+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;
+            }
+        }
+    }
+}

+ 44 - 0
src/Core/ServiceWrapper/Extensions/IWinSWExtension.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Xml;
+using winsw.Util;
+
+namespace winsw.Extensions
+{
+    /// <summary>
+    /// Interface for Win Service Wrapper Extension
+    /// </summary>
+    /// <remarks>All implementations should provide the default empty constructor. The initialization will be performed by Init methods</remarks>
+    public interface IWinSWExtension
+    {
+        /// <summary>
+        /// Extension name to be displayed in logs
+        /// </summary>
+        String DisplayName { get; }
+
+        /// <summary>
+        /// Extension descriptor
+        /// </summary>
+        WinSWExtensionDescriptor Descriptor { get; set; }
+
+        /// <summary>
+        /// Init handler. Extension should load it's config during that step
+        /// </summary>
+        /// <param name="descriptor">Service descriptor</param>
+        /// <param name="node">Configuration node</param>
+        void Configure(ServiceDescriptor descriptor, XmlNode node, IEventWriter logger);
+
+        /// <summary>
+        /// Start handler. Called during start of the service
+        /// </summary>
+        /// <param name="logger">Logger</param>
+        /// <exception cref="ExtensionException">Any error during execution</exception>
+        void OnStart(IEventWriter logger);
+
+        /// <summary>
+        /// Stop handler. Called during stop of the service
+        /// </summary>
+        /// <param name="logger">Logger</param>
+        /// <exception cref="ExtensionException">Any error during execution</exception>
+        void OnStop(IEventWriter logger);
+    }
+}

+ 39 - 0
src/Core/ServiceWrapper/Extensions/WinSWExtensionDescriptor.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Xml;
+using winsw.Util;
+
+namespace winsw.Extensions
+{
+    public class WinSWExtensionDescriptor
+    {
+        /// <summary>
+        /// Unique extension ID
+        /// </summary>
+        public String Id { get; private set; }
+
+        /// <summary>
+        /// Exception is enabled
+        /// </summary>
+        public bool Enabled { get; private set; }
+
+        /// <summary>
+        /// Extension classname
+        /// </summary>
+        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<string>(node, "className");
+            string id = XmlHelper.SingleAttribute<string>(node, "id");
+            return new WinSWExtensionDescriptor(id, className, enabled);
+        }
+    }
+}

+ 123 - 0
src/Core/ServiceWrapper/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<string, IWinSWExtension> Extensions { private set; get; }
+        public ServiceDescriptor ServiceDescriptor { private set; get; }
+
+        public WinSWExtensionManager(ServiceDescriptor serviceDescriptor)
+        {
+            ServiceDescriptor = serviceDescriptor;
+            Extensions = new Dictionary<string, IWinSWExtension>();
+        }
+
+        /// <summary>
+        /// Starts all extensions
+        /// </summary>
+        /// <exception cref="ExtensionException">Start failure</exception>
+        public void OnStart(IEventWriter logger) 
+        {
+            foreach (var ext in Extensions)
+            {
+                ext.Value.OnStart(logger);     
+            }
+        }
+
+        /// <summary>
+        /// Stops all extensions
+        /// </summary>
+        /// <exception cref="ExtensionException">Stop failure</exception>
+        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);
+            }
+        }
+
+        /// <summary>
+        /// Loads extensions from the configuration file
+        /// </summary>
+        /// <param name="id">Extension ID</param>
+        /// <param name="logger">Logger</param>
+        /// <exception cref="ExtensionException">Loading failure</exception>
+        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   
+    }
+}

+ 13 - 0
src/Core/ServiceWrapper/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);
+    }
+}

+ 33 - 1
src/Core/ServiceWrapper/Main.cs

@@ -14,13 +14,15 @@ 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 System.Reflection;
 
 namespace winsw
 {
-    public class WrapperService : ServiceBase, EventLogger
+    public class WrapperService : ServiceBase, EventLogger, IEventWriter
     {
         private SERVICE_STATUS _wrapperServiceStatus;
 
@@ -28,6 +30,8 @@ namespace winsw
         private readonly ServiceDescriptor _descriptor;
         private Dictionary<string, string> _envs;
 
+        internal WinSWExtensionManager ExtensionManager { private set; get; }
+
         private static readonly ILog Log = LogManager.GetLogger("WinSW");
 
         /// <summary>
@@ -52,6 +56,7 @@ namespace winsw
         {
             _descriptor = descriptor;
             ServiceName = _descriptor.Id;
+            ExtensionManager = new WinSWExtensionManager(_descriptor);
             CanShutdown = true;
             CanStop = true;
             CanPauseAndContinue = false;
@@ -246,6 +251,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 +348,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();

+ 33 - 3
src/Core/ServiceWrapper/ServiceDescriptor.cs

@@ -5,6 +5,7 @@ using System.Diagnostics;
 using System.IO;
 using System.Reflection;
 using System.Xml;
+using winsw.Util;
 using WMI;
 
 namespace winsw
@@ -227,9 +228,7 @@ namespace winsw
             }
         }
 
-        /// <summary>
-        /// Optional working directory.
-        /// </summary>
+        
         public string WorkingDirectory {
             get {
                 var wd = SingleElement("workingdirectory", true);
@@ -237,6 +236,37 @@ namespace winsw
             }
         }
 
+        public List<string> ExtensionIds
+        {
+            get
+            {
+                List<string> res = new List<string>();
+
+                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<string>(extension, "id");
+                        res.Add(extensionId);
+                    }
+                }
+
+                return res;
+            }
+        }
+
+        public XmlNode ExtensionsConfiguration
+        {
+            get
+            {
+                XmlNode argumentNode = dom.SelectSingleNode("//extensions");
+                return argumentNode;
+            }
+        }
+
         /// <summary>
         /// Combines the contents of all the elements of the given name,
         /// or return null if no element exists. Handles whitespace quotation.

+ 13 - 0
src/Core/ServiceWrapper/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);
+    }
+}

+ 15 - 0
src/Core/ServiceWrapper/Util/WinSWException.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace winsw.Util
+{
+    public class WinSWException : Exception
+    {
+        public WinSWException(String message)
+            : base(message)
+        { }
+
+        public WinSWException(String message, Exception innerException)
+            : base(message, innerException)
+        { }
+    }
+}

+ 73 - 0
src/Core/ServiceWrapper/Util/XmlHelper.cs

@@ -0,0 +1,73 @@
+using System;
+using System.IO;
+using System.Xml;
+
+namespace winsw.Util
+{
+    public class XmlHelper
+    {
+        /// <summary>
+        /// Retrieves a single string element 
+        /// </summary>
+        /// <param name="node">Parent node</param>
+        /// <param name="tagName">Element name</param>
+        /// <param name="optional">If otional, don't throw an exception if the elemen is missing</param>
+        /// <returns>String value or null</returns>
+        /// <exception cref="InvalidDataException">The required element is missing</exception>
+        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);
+        }
+
+        /// <summary>
+        /// Retrieves a single node
+        /// </summary>
+        /// <param name="node">Parent node</param>
+        /// <param name="tagName">Element name</param>
+        /// <param name="optional">If otional, don't throw an exception if the elemen is missing</param>
+        /// <returns>String value or null</returns>
+        /// <exception cref="InvalidDataException">The required element is missing</exception>
+        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;
+        }
+
+        /// <summary>
+        /// Retrieves a single mandatory attribute
+        /// </summary>
+        /// <param name="node">Parent node</param>
+        /// <param name="attributeName">Attribute name</param>
+        /// <returns>Attribute value</returns>
+        /// <exception cref="InvalidDataException">The required attribute is missing</exception>
+        public static TAttributeType SingleAttribute <TAttributeType> (XmlElement node, string attributeName)
+        {
+            if (!node.HasAttribute(attributeName))
+            {
+                throw new InvalidDataException("Attribute <" + attributeName + "> is missing in configuration XML");
+            }
+
+            return SingleAttribute(node, attributeName, default(TAttributeType));
+        }
+
+        /// <summary>
+        /// Retrieves a single optional attribute
+        /// </summary>
+        /// <param name="node">Parent node</param>
+        /// <param name="attributeName">Attribute name</param>
+        /// <param name="defaultValue">Default value</param>
+        /// <returns>Attribute value (or default)</returns>
+        public static TAttributeType SingleAttribute<TAttributeType>(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;
+        }
+    }
+}

+ 17 - 0
src/Core/ServiceWrapper/WinSWException.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace winsw.extensions
+{
+    public class WinSWException : Exception
+    {
+        public WinSWException(String message)
+            : base(message)
+        { }
+
+        public WinSWException(String message, Exception innerException)
+            : base(message, innerException)
+        { }
+    }
+}

+ 75 - 0
src/Core/ServiceWrapper/XmlHelper.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+using System.IO;
+
+namespace winsw.Utils
+{
+    internal class XmlHelper
+    {
+        /// <summary>
+        /// Retrieves a single string element 
+        /// </summary>
+        /// <param name="node">Parent node</param>
+        /// <param name="tagName">Element name</param>
+        /// <param name="optional">If otional, don't throw an exception if the elemen is missing</param>
+        /// <returns>String value or null</returns>
+        /// <exception cref="InvalidDataException">The required element is missing</exception>
+        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);
+        }
+
+        /// <summary>
+        /// Retrieves a single node
+        /// </summary>
+        /// <param name="node">Parent node</param>
+        /// <param name="tagName">Element name</param>
+        /// <param name="optional">If otional, don't throw an exception if the elemen is missing</param>
+        /// <returns>String value or null</returns>
+        /// <exception cref="InvalidDataException">The required element is missing</exception>
+        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;
+        }
+
+        /// <summary>
+        /// Retrieves a single mandatory attribute
+        /// </summary>
+        /// <param name="node">Parent node</param>
+        /// <param name="attributeName">Attribute name</param>
+        /// <returns>Attribute value</returns>
+        /// <exception cref="InvalidDataException">The required attribute is missing</exception>
+        public static TAttributeType SingleAttribute <TAttributeType> (XmlElement node, string attributeName)
+        {
+            if (!node.HasAttribute(attributeName))
+            {
+                throw new InvalidDataException("Attribute <" + attributeName + "> is missing in configuration XML");
+            }
+
+            return SingleAttribute<TAttributeType>(node, attributeName, default(TAttributeType));
+        }
+
+        /// <summary>
+        /// Retrieves a single optional attribute
+        /// </summary>
+        /// <param name="node">Parent node</param>
+        /// <param name="attributeName">Attribute name</param>
+        /// <param name="defaultValue">Default value</param>
+        /// <returns>Attribute value (or default)</returns>
+        public static TAttributeType SingleAttribute<TAttributeType>(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;
+        }
+    }
+}

+ 9 - 0
src/Core/ServiceWrapper/winsw.csproj

@@ -77,6 +77,11 @@
     <Compile Include="Advapi32.cs" />
     <Compile Include="Download.cs" />
     <Compile Include="DynamicProxy.cs" />
+    <Compile Include="Extensions\AbstractWinSWExtension.cs" />
+    <Compile Include="Extensions\ExtensionException.cs" />
+    <Compile Include="Extensions\IWinSWExtension.cs" />
+    <Compile Include="Extensions\WinSWExtensionDescriptor.cs" />
+    <Compile Include="Extensions\WinSWExtensionManager.cs" />
     <Compile Include="Kernel32.cs" />
     <Compile Include="LogAppenders.cs" />
     <Compile Include="Main.cs">
@@ -86,6 +91,9 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ServiceDescriptor.cs" />
     <Compile Include="SigIntHelper.cs" />
+    <Compile Include="Util\IEventWriter.cs" />
+    <Compile Include="Util\WinSWException.cs" />
+    <Compile Include="Util\XmlHelper.cs" />
     <Compile Include="Wmi.cs" />
     <Compile Include="WmiSchema.cs" />
   </ItemGroup>
@@ -114,6 +122,7 @@
       <Install>true</Install>
     </BootstrapperPackage>
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 36 - 0
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")]

+ 93 - 0
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<SharedDirectoryMapperConfig> _entries = new List<SharedDirectoryMapperConfig>();
+
+        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);
+        }
+    }
+}

+ 57 - 0
src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>winsw.Plugins.SharedDirectoryMapper</RootNamespace>
+    <AssemblyName>SharedDirectoryMapper</AssemblyName>
+    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="SharedDirectoryMapper.cs" />
+    <Compile Include="SharedDirectoryMapperConfig.cs" />
+    <Compile Include="SharedDirectoryMapperHelper.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\Core\ServiceWrapper\winsw.csproj">
+      <Project>{0de77f55-ade5-43c1-999a-0bc81153b039}</Project>
+      <Name>winsw</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 31 - 0
src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperConfig.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Xml;
+using winsw.Util;
+
+namespace winsw.Plugins.SharedDirectoryMapper
+{
+    /// <summary>
+    /// Stores configuration entries for SharedDirectoryMapper extension.
+    /// </summary>
+    internal 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<string>(node, "label");
+            string uncPath = XmlHelper.SingleAttribute<string>(node, "uncpath");
+            return new SharedDirectoryMapperConfig(enableMapping, label, uncPath);
+        }
+    }
+}

+ 73 - 0
src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperHelper.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Diagnostics;
+using winsw.Util;
+
+namespace winsw.Plugins.SharedDirectoryMapper
+{
+    class SharedDirectoryMappingHelper
+    {
+        /// <summary>
+        /// Invokes a system command
+        /// </summary>
+        /// <see cref="SharedDirectoryMapper"/>
+        /// <param name="command">Command to be executed</param>
+        /// <param name="args">Command arguments</param>
+        /// <exception cref="MapperException">Operation failure</exception>
+        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);
+            }
+        }
+
+        /// <summary>
+        /// Maps the remote directory
+        /// </summary>
+        /// <param name="label">Disk label</param>
+        /// <param name="uncPath">UNC path to the directory</param>
+        /// <exception cref="MapperException">Operation failure</exception>
+        public void MapDirectory(String label, String uncPath)
+        {
+            InvokeCommand("net.exe", " use " + label + " " + uncPath);
+        }
+
+        /// <summary>
+        /// Unmaps the label
+        /// </summary>
+        /// <param name="label">Disk label</param>
+        /// <exception cref="MapperException">Operation failure</exception>
+        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;
+        }
+    }
+}

+ 18 - 0
src/Plugins/SharedDirectoryMapper/sampleConfig.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<service>
+  <id>SERVICE_NAME</id>
+  <name>Jenkins Slave</name>
+  <description>This service runs a slave for Jenkins continuous integration system.</description>
+  <executable>C:\Program Files\Java\jre7\bin\java.exe</executable>
+  <arguments>-Xrs  -jar "%BASE%\slave.jar" -jnlpUrl ...</arguments>
+  <logmode>rotate</logmode>
+
+  <extensions>
+    <extension enabled="true" className="winsw.extensions.shared_dirs.SharedDirectoryMapper" id="mapNetworDirs">
+      <mapping>
+        <map enabled="false" label="N:" uncpath="\\UNC"/>
+        <map enabled="false" label="M:" uncpath="\\UNC2"/>
+      </mapping>
+    </extension>
+  </extensions>
+</service>

+ 62 - 0
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 = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+                + "<service>                                                                                                        "
+                + "  <id>SERVICE_NAME</id>                                                                                          "
+                + "  <name>Jenkins Slave</name>                                                                                     "
+                + "  <description>This service runs a slave for Jenkins continuous integration system.</description>                "
+                + "  <executable>C:\\Program Files\\Java\\jre7\\bin\\java.exe</executable>                                               "
+                + "  <arguments>-Xrs  -jar \\\"%BASE%\\slave.jar\\\" -jnlpUrl ...</arguments>                                              "
+                + "  <logmode>rotate</logmode>                                                                                      "
+                + "  <extensions>                                                                                                   "
+                + "    <extension enabled=\"true\" className=\"" + testExtension + "\" id=\"mapNetworDirs\"> "
+                + "      <mapping>                                                                                                  "
+                + "        <map enabled=\"false\" label=\"N:\" uncpath=\"\\\\UNC\"/>                                                        "
+                + "        <map enabled=\"false\" label=\"M:\" uncpath=\"\\\\UNC2\"/>                                                       "
+                + "      </mapping>                                                                                                 "
+                + "    </extension>         "
+                + "    <extension enabled=\"true\" className=\"" + testExtension + "\" id=\"mapNetworDirs2\"> "
+                + "      <mapping>                                                                                                  "
+                + "        <map enabled=\"false\" label=\"X:\" uncpath=\"\\\\UNC\"/>                                                        "
+                + "        <map enabled=\"false\" label=\"Y:\" uncpath=\"\\\\UNC2\"/>                                                       "
+                + "      </mapping>                                                                                                 "
+                + "    </extension>         "
+                + "  </extensions>                                                                                                  "
+                + "</service>";
+            _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);
+        }
+    }
+}

+ 7 - 0
src/Test/winswTests/NunitTest.nunit

@@ -0,0 +1,7 @@
+<NUnitProject>
+ <Settings activeconfig="Debug" />
+ <Config name="Debug">
+ <assembly path="bin\Debug\winswTests.dll" />
+ </Config>
+ <Config name="Release"></Config>
+</NUnitProject>

+ 19 - 0
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);
+        }
+    }
+}

+ 7 - 0
src/Test/winswTests/winswTests.csproj

@@ -52,9 +52,11 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Extensions\WinSWExtensionManagerTest.cs" />
     <Compile Include="MainTest.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ServiceDescriptorTests.cs" />
+    <Compile Include="Util\TestLogger.cs" />
     <Compile Include="Util\CLITestHelper.cs" />
   </ItemGroup>
   <ItemGroup>
@@ -62,10 +64,15 @@
       <Project>{0DE77F55-ADE5-43C1-999A-0BC81153B039}</Project>
       <Name>winsw</Name>
     </ProjectReference>
+    <ProjectReference Include="..\..\Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj">
+      <Project>{ca5c71db-c5a8-4c27-bf83-8e6daed9d6b5}</Project>
+      <Name>SharedDirectoryMapper</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <None Include="packages.config" />
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

+ 45 - 0
src/winsw.sln

@@ -6,6 +6,9 @@ MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}"
+	ProjectSection(ProjectDependencies) = postProject
+		{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5} = {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}
+	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6BDF4025-D46C-4C69-BDB2-5CE434C857AA}"
 	ProjectSection(SolutionItems) = preProject
@@ -14,22 +17,64 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6BDF40
 		.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
 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 = Debug|Any CPU
+		{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Mixed Platforms.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|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
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{93843402-842B-44B4-B303-AEE829BE0B43} = {077C2CEC-B687-4B53-86E9-C1A1BF5554E5}
+		{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D}
+	EndGlobalSection
 EndGlobal

+ 3 - 0
src/winsw.sln.DotSettings

@@ -0,0 +1,3 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SW/@EntryIndexedValue">SW</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UNC/@EntryIndexedValue">UNC</s:String></wpf:ResourceDictionary>