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 <o.v.nenashev@gmail.com>

Conflicts:
	src/Core/ServiceWrapper/Main.cs

Conflicts:
	src/Core/ServiceWrapper/ServiceDescriptor.cs
	src/Test/winswTests/winswTests.csproj
	src/winsw.sln
pull/74/head
Oleg Nenashev 2015-01-31 19:50:42 +03:00
parent 4a2249d30c
commit 2546168ed3
26 changed files with 995 additions and 4 deletions

View File

@ -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
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -14,13 +14,15 @@ using log4net.Core;
using log4net.Layout; using log4net.Layout;
using log4net.Repository.Hierarchy; using log4net.Repository.Hierarchy;
using Microsoft.Win32; using Microsoft.Win32;
using winsw.Extensions;
using winsw.Util;
using WMI; using WMI;
using ServiceType = WMI.ServiceType; using ServiceType = WMI.ServiceType;
using System.Reflection; using System.Reflection;
namespace winsw namespace winsw
{ {
public class WrapperService : ServiceBase, EventLogger public class WrapperService : ServiceBase, EventLogger, IEventWriter
{ {
private SERVICE_STATUS _wrapperServiceStatus; private SERVICE_STATUS _wrapperServiceStatus;
@ -28,6 +30,8 @@ namespace winsw
private readonly ServiceDescriptor _descriptor; private readonly ServiceDescriptor _descriptor;
private Dictionary<string, string> _envs; private Dictionary<string, string> _envs;
internal WinSWExtensionManager ExtensionManager { private set; get; }
private static readonly ILog Log = LogManager.GetLogger("WinSW"); private static readonly ILog Log = LogManager.GetLogger("WinSW");
/// <summary> /// <summary>
@ -52,6 +56,7 @@ namespace winsw
{ {
_descriptor = descriptor; _descriptor = descriptor;
ServiceName = _descriptor.Id; ServiceName = _descriptor.Id;
ExtensionManager = new WinSWExtensionManager(_descriptor);
CanShutdown = true; CanShutdown = true;
CanStop = true; CanStop = true;
CanPauseAndContinue = false; CanPauseAndContinue = false;
@ -246,6 +251,22 @@ namespace winsw
LogEvent("Starting " + _descriptor.Executable + ' ' + startarguments); LogEvent("Starting " + _descriptor.Executable + ' ' + startarguments);
WriteEvent("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); StartProcess(_process, startarguments, _descriptor.Executable);
// send stdout and stderr to its respective output file. // send stdout and stderr to its respective output file.
@ -327,6 +348,17 @@ namespace winsw
SignalShutdownComplete(); 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) if (_systemShuttingdown && _descriptor.BeepOnShutdown)
{ {
Console.Beep(); Console.Beep();

View File

@ -5,6 +5,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Xml; using System.Xml;
using winsw.Util;
using WMI; using WMI;
namespace winsw namespace winsw
@ -227,9 +228,7 @@ namespace winsw
} }
} }
/// <summary>
/// Optional working directory.
/// </summary>
public string WorkingDirectory { public string WorkingDirectory {
get { get {
var wd = SingleElement("workingdirectory", true); 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> /// <summary>
/// Combines the contents of all the elements of the given name, /// Combines the contents of all the elements of the given name,
/// or return null if no element exists. Handles whitespace quotation. /// or return null if no element exists. Handles whitespace quotation.

View File

@ -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);
}
}

View File

@ -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)
{ }
}
}

View File

@ -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;
}
}
}

View File

@ -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)
{ }
}
}

View File

@ -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;
}
}
}

View File

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

View File

@ -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")]

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

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

View File

@ -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);
}
}
}

View File

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

View File

@ -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}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}"
ProjectSection(ProjectDependencies) = postProject
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5} = {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}
EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6BDF4025-D46C-4C69-BDB2-5CE434C857AA}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6BDF4025-D46C-4C69-BDB2-5CE434C857AA}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@ -14,22 +17,64 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6BDF40
.nuget\NuGet.targets = .nuget\NuGet.targets .nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection EndProjectSection
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|Win32 = Debug|Win32 Debug|Win32 = Debug|Win32
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|Win32 = Release|Win32 Release|Win32 = Release|Win32
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.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.ActiveCfg = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.Build.0 = Debug|Any CPU {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.Build.0 = Debug|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.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.ActiveCfg = Release|Any CPU
{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.Build.0 = Release|Any CPU {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.Build.0 = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.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.ActiveCfg = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.Build.0 = Debug|Any CPU {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.Build.0 = Debug|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.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.ActiveCfg = Release|Any CPU
{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.Build.0 = Release|Any CPU {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.Build.0 = Release|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection 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 EndGlobal

View File

@ -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>