Browse Source

Merge pull request #71 from kohsuke/winsw-2.0

WinSW 2.0
Oleg Nenashev 9 years ago
parent
commit
929b87c383
54 changed files with 1540 additions and 62 deletions
  1. 3 0
      .gitignore
  2. BIN
      Tests/Lib/nunit.framework.dll
  3. 6 0
      src/.nuget/NuGet.Config
  4. BIN
      src/.nuget/NuGet.exe
  5. 144 0
      src/.nuget/NuGet.targets
  6. 4 0
      src/.nuget/packages.config
  7. 100 14
      src/Core/ServiceWrapper/Main.cs
  8. 0 0
      src/Core/ServiceWrapper/Properties/AssemblyInfo.cs
  9. 0 0
      src/Core/ServiceWrapper/SigIntHelper.cs
  10. 0 0
      src/Core/ServiceWrapper/manifest.xml
  11. 6 0
      src/Core/ServiceWrapper/packages.config
  12. 0 0
      src/Core/ServiceWrapper/pom.xml
  13. 179 0
      src/Core/ServiceWrapper/winsw.csproj
  14. 0 0
      src/Core/ServiceWrapper/winsw.xml
  15. 0 0
      src/Core/WinSWCore/Download.cs
  16. 0 0
      src/Core/WinSWCore/DynamicProxy.cs
  17. 27 0
      src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs
  18. 29 0
      src/Core/WinSWCore/Extensions/ExtensionException.cs
  19. 16 0
      src/Core/WinSWCore/Extensions/ExtensionPointAttribute.cs
  20. 47 0
      src/Core/WinSWCore/Extensions/IWinSWExtension.cs
  21. 45 0
      src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs
  22. 123 0
      src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs
  23. 0 0
      src/Core/WinSWCore/LogAppenders.cs
  24. 6 6
      src/Core/WinSWCore/Native/Advapi32.cs
  25. 7 7
      src/Core/WinSWCore/Native/Kernel32.cs
  26. 0 0
      src/Core/WinSWCore/PeriodicRollingCalendar.cs
  27. 36 0
      src/Core/WinSWCore/Properties/AssemblyInfo.cs
  28. 34 3
      src/Core/WinSWCore/ServiceDescriptor.cs
  29. 13 0
      src/Core/WinSWCore/Util/IEventWriter.cs
  30. 75 0
      src/Core/WinSWCore/Util/XmlHelper.cs
  31. 89 0
      src/Core/WinSWCore/WinSWCore.csproj
  32. 17 0
      src/Core/WinSWCore/WinSWException.cs
  33. 0 0
      src/Core/WinSWCore/Wmi.cs
  34. 0 0
      src/Core/WinSWCore/WmiSchema.cs
  35. 4 0
      src/Core/WinSWCore/packages.config
  36. 36 0
      src/Plugins/SharedDirectoryMapper/Properties/AssemblyInfo.cs
  37. 93 0
      src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs
  38. 57 0
      src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj
  39. 31 0
      src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperConfig.cs
  40. 73 0
      src/Plugins/SharedDirectoryMapper/SharedDirectoryMapperHelper.cs
  41. 18 0
      src/Plugins/SharedDirectoryMapper/sampleConfig.xml
  42. 62 0
      src/Test/winswTests/Extensions/WinSWExtensionManagerTest.cs
  43. 0 0
      src/Test/winswTests/MainTest.cs
  44. 7 0
      src/Test/winswTests/NunitTest.nunit
  45. 0 0
      src/Test/winswTests/Properties/AssemblyInfo.cs
  46. 0 0
      src/Test/winswTests/ServiceDescriptorTests.cs
  47. 0 0
      src/Test/winswTests/Util/CLITestHelper.cs
  48. 19 0
      src/Test/winswTests/Util/TestLogger.cs
  49. 1 0
      src/Test/winswTests/packages.config
  50. 24 4
      src/Test/winswTests/winswTests.csproj
  51. 6 0
      src/packages/repositories.config
  52. 100 0
      src/winsw.sln
  53. 3 0
      src/winsw.sln.DotSettings
  54. 0 28
      winsw.sln

+ 3 - 0
.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

BIN
Tests/Lib/nunit.framework.dll


+ 6 - 0
src/.nuget/NuGet.Config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <solution>
+    <add key="disableSourceControlIntegration" value="true" />
+  </solution>
+</configuration>

BIN
src/.nuget/NuGet.exe


+ 144 - 0
src/.nuget/NuGet.targets

@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
+
+        <!-- Enable the restore command to run before builds -->
+        <RestorePackages Condition="  '$(RestorePackages)' == '' ">false</RestorePackages>
+
+        <!-- Property that enables building a package from a project -->
+        <BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
+
+        <!-- Determines if package restore consent is required to restore packages -->
+        <RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
+
+        <!-- Download NuGet.exe if it does not already exist -->
+        <DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
+    </PropertyGroup>
+
+    <ItemGroup Condition=" '$(PackageSources)' == '' ">
+        <!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
+        <!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
+        <!--
+            <PackageSource Include="https://www.nuget.org/api/v2/" />
+            <PackageSource Include="https://my-nuget-source/nuget/" />
+        -->
+    </ItemGroup>
+
+    <PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
+        <!-- Windows specific commands -->
+        <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
+    </PropertyGroup>
+
+    <PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
+        <!-- We need to launch nuget.exe with the mono command if we're not on windows -->
+        <NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <PackagesProjectConfig Condition=" '$(OS)' == 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config</PackagesProjectConfig>
+        <PackagesProjectConfig Condition=" '$(OS)' != 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config</PackagesProjectConfig>
+    </PropertyGroup>
+
+    <PropertyGroup>
+      <PackagesConfig Condition="Exists('$(MSBuildProjectDirectory)\packages.config')">$(MSBuildProjectDirectory)\packages.config</PackagesConfig>
+      <PackagesConfig Condition="Exists('$(PackagesProjectConfig)')">$(PackagesProjectConfig)</PackagesConfig>
+    </PropertyGroup>
+    
+    <PropertyGroup>
+        <!-- NuGet command -->
+        <NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
+        <PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
+
+        <NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
+        <NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 "$(NuGetExePath)"</NuGetCommand>
+
+        <PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
+
+        <RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
+        <NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>
+
+        <PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir>
+        <PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir>
+
+        <!-- Commands -->
+        <RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)"  $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
+        <BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>
+
+        <!-- We need to ensure packages are restored prior to assembly resolve -->
+        <BuildDependsOn Condition="$(RestorePackages) == 'true'">
+            RestorePackages;
+            $(BuildDependsOn);
+        </BuildDependsOn>
+
+        <!-- Make the build depend on restore packages -->
+        <BuildDependsOn Condition="$(BuildPackage) == 'true'">
+            $(BuildDependsOn);
+            BuildPackage;
+        </BuildDependsOn>
+    </PropertyGroup>
+
+    <Target Name="CheckPrerequisites">
+        <!-- Raise an error if we're unable to locate nuget.exe  -->
+        <Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
+        <!--
+        Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
+        This effectively acts as a lock that makes sure that the download operation will only happen once and all
+        parallel builds will have to wait for it to complete.
+        -->
+        <MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />
+    </Target>
+
+    <Target Name="_DownloadNuGet">
+        <DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
+    </Target>
+
+    <Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">        
+        <Exec Command="$(RestoreCommand)"
+              Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
+
+        <Exec Command="$(RestoreCommand)"
+              LogStandardErrorAsError="true"
+              Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
+    </Target>
+
+    <Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
+        <Exec Command="$(BuildCommand)"
+              Condition=" '$(OS)' != 'Windows_NT' " />
+
+        <Exec Command="$(BuildCommand)"
+              LogStandardErrorAsError="true"
+              Condition=" '$(OS)' == 'Windows_NT' " />
+    </Target>
+
+    <UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
+        <ParameterGroup>
+            <OutputFilename ParameterType="System.String" Required="true" />
+        </ParameterGroup>
+        <Task>
+            <Reference Include="System.Core" />
+            <Using Namespace="System" />
+            <Using Namespace="System.IO" />
+            <Using Namespace="System.Net" />
+            <Using Namespace="Microsoft.Build.Framework" />
+            <Using Namespace="Microsoft.Build.Utilities" />
+            <Code Type="Fragment" Language="cs">
+                <![CDATA[
+                try {
+                    OutputFilename = Path.GetFullPath(OutputFilename);
+
+                    Log.LogMessage("Downloading latest version of NuGet.exe...");
+                    WebClient webClient = new WebClient();
+                    webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
+
+                    return true;
+                }
+                catch (Exception ex) {
+                    Log.LogErrorFromException(ex);
+                    return false;
+                }
+            ]]>
+            </Code>
+        </Task>
+    </UsingTask>
+</Project>

+ 4 - 0
src/.nuget/packages.config

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="ILMerge.MSBuild.Tasks" version="1.0.0.3" />
+</packages>

+ 100 - 14
Main.cs → src/Core/ServiceWrapper/Main.cs

@@ -7,14 +7,23 @@ using System.Runtime.InteropServices;
 using System.ServiceProcess;
 using System.Text;
 using System.Threading;
+using log4net;
+using log4net.Appender;
+using log4net.Config;
+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;
 
@@ -22,6 +31,10 @@ 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>
         /// Indicates to the watch dog thread that we are going to terminate the process,
         /// so don't try to kill us when the child exits.
@@ -44,6 +57,7 @@ namespace winsw
         {
             _descriptor = descriptor;
             ServiceName = _descriptor.Id;
+            ExtensionManager = new WinSWExtensionManager(_descriptor);
             CanShutdown = true;
             CanStop = true;
             CanPauseAndContinue = false;
@@ -183,22 +197,19 @@ namespace winsw
 
         private void WriteEvent(Exception exception)
         {
-            WriteEvent(exception.Message + "\nStacktrace:" + exception.StackTrace);
+            //TODO: pass exception to logger
+            WriteEvent(exception.Message + "\nStacktrace:" + exception.StackTrace, Level.Error);
         }
 
         private void WriteEvent(String message, Exception exception)
         {
-            WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace);
+            //TODO: pass exception to logger
+            WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace, Level.Error);
         }
 
-        private void WriteEvent(String message)
+        private void WriteEvent(String message, Level logLevel = null, Exception ex = null)
         {
-            string logfilename = Path.Combine(_descriptor.LogDirectory, _descriptor.BaseName + ".wrapper.log");
-            StreamWriter log = new StreamWriter(logfilename, true);
-
-            log.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
-            log.Flush();
-            log.Close();
+            Log.Logger.Log(GetType(), logLevel ?? Level.Info, message, ex);
         }
 
         protected override void OnStart(string[] _)
@@ -241,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.
@@ -322,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();
@@ -389,7 +427,7 @@ namespace winsw
             {
                 try
                 {
-                    WriteEvent("SIGINT to " + pid + " failed - Killing as fallback");
+                    WriteEvent("SIGINT to " + pid + " failed - Killing as fallback", Level.Warn);
                     proc.Kill();
                 }
                 catch (ArgumentException)
@@ -504,6 +542,7 @@ namespace winsw
 
         public static int Main(string[] args)
         {
+            // Run app
             try
             {
                 Run(args);
@@ -529,9 +568,18 @@ namespace winsw
         // ReSharper disable once InconsistentNaming
         public static void Run(string[] _args, ServiceDescriptor descriptor = null)
         {
-            if (_args.Length > 0)
-            {
-                var d = descriptor ?? new ServiceDescriptor();
+            bool isCLIMode = _args.Length > 0;
+            var d = descriptor ?? new ServiceDescriptor();
+
+            // Configure the wrapper-internal logging
+            // STDIN and STDOUT of the child process will be handled independently
+            InitLoggers(d, isCLIMode);
+
+            if (isCLIMode) // CLI mode
+            {               
+                Log.Debug("Starting ServiceWrapper in CLI mode");
+
+                // Get service info for the future use
                 Win32Services svc = new WmiRoot().GetCollection<Win32Services>();
                 Win32Service s = svc.Select(d.Id);
 
@@ -702,6 +750,7 @@ namespace winsw
                 }
                 if (args[0] == "status")
                 {
+                    Log.Warn("User requested the status");
                     if (s == null)
                         Console.WriteLine("NonExistent");
                     else if (s.Started)
@@ -738,6 +787,43 @@ namespace winsw
             Run(new WrapperService());
         }
 
+        private static void InitLoggers(ServiceDescriptor d, bool enableCLILogging)
+        {
+            Level logLevel = Level.Debug;
+
+            // Legacy format from winsw-1.x: (DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
+            PatternLayout pl = new PatternLayout { ConversionPattern = "%d %-5p - %m%n" };
+            pl.ActivateOptions();
+
+            // wrapper.log
+            String wrapperLogPath = Path.Combine(d.LogDirectory, d.BaseName + ".wrapper.log");
+            var wrapperLog = new FileAppender
+            {
+                AppendToFile = true,
+                File = wrapperLogPath,
+                ImmediateFlush = true,
+                Name = "Wrapper file log",
+                Threshold = logLevel,
+                LockingModel = new FileAppender.MinimalLock(),
+                Layout = pl
+            };
+            wrapperLog.ActivateOptions();
+            BasicConfigurator.Configure(wrapperLog);
+
+            // Also display logs in CLI if required
+            if (enableCLILogging)
+            {
+                var consoleAppender = new ConsoleAppender
+                {
+                    Name = "Wrapper console log", 
+                    Threshold = logLevel,
+                    Layout = pl               
+                };
+                consoleAppender.ActivateOptions();
+                ((Logger)Log.Logger).AddAppender(consoleAppender);
+            }
+        }
+
         private static string ReadPassword()
         {
             StringBuilder buf = new StringBuilder();

+ 0 - 0
Properties/AssemblyInfo.cs → src/Core/ServiceWrapper/Properties/AssemblyInfo.cs


+ 0 - 0
SigIntHelper.cs → src/Core/ServiceWrapper/SigIntHelper.cs


+ 0 - 0
manifest.xml → src/Core/ServiceWrapper/manifest.xml


+ 6 - 0
src/Core/ServiceWrapper/packages.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="ILMerge" version="2.14.1208" targetFramework="net20" />
+  <package id="log4net" version="2.0.3" targetFramework="net20" />
+  <package id="MSBuildTasks" version="1.4.0.88" targetFramework="net20" />
+</packages>

+ 0 - 0
pom.xml → src/Core/ServiceWrapper/pom.xml


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

@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>9.0.21022</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{0DE77F55-ADE5-43C1-999A-0BC81153B039}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>winsw</RootNamespace>
+    <AssemblyName>WindowsService</AssemblyName>
+    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <StartupObject>
+    </StartupObject>
+    <SignManifests>false</SignManifests>
+    <SignAssembly>true</SignAssembly>
+    <AssemblyOriginatorKeyFile>$(SolutionDir)..\winsw_key.snk</AssemblyOriginatorKeyFile>
+    <FileUpgradeFlags>
+    </FileUpgradeFlags>
+    <UpgradeBackupLocation>
+    </UpgradeBackupLocation>
+    <OldToolsVersion>3.5</OldToolsVersion>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <RestorePackages>true</RestorePackages>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>TRACE;DEBUG</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </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>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <DocumentationFile>
+    </DocumentationFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="log4net">
+      <HintPath>..\..\packages\log4net.2.0.3\lib\net20-full\log4net.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Management" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.ServiceProcess" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Main.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="SigIntHelper.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="manifest.xml" />
+    <Content Include="pom.xml">
+      <SubType>Designer</SubType>
+    </Content>
+    <Content Include="winsw.xml">
+      <SubType>Designer</SubType>
+    </Content>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="..\..\..\winsw_cert.pfx" />
+    <None Include="packages.config">
+      <SubType>Designer</SubType>
+    </None>
+    <None Include="$(AssemblyOriginatorKeyFile)" />
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj">
+      <Project>{ca5c71db-c5a8-4c27-bf83-8e6daed9d6b5}</Project>
+      <Name>SharedDirectoryMapper</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\WinSWCore\WinSWCore.csproj">
+      <Project>{9d0c63e2-b6ff-4a85-bd36-b3e5d7f27d06}</Project>
+      <Name>WinSWCore</Name>
+    </ProjectReference>
+  </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.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>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}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
+  <!-- Merge plugins and other DLLs into winsw.exe -->
+  <UsingTask TaskName="MSBuild.Community.Tasks.ILMerge" AssemblyFile="$(SolutionDir)\packages\MSBuildTasks.1.4.0.88\tools\MSBuild.Community.Tasks.dll" />
+  <Target Name="AfterBuild">
+    <ItemGroup>
+      <MergeAsm Include="$(OutputPath)$(TargetFileName)" />
+      <MergeAsm Include="$(OutputPath)WinSWCore.dll" />
+      <MergeAsm Include="$(OutputPath)SharedDirectoryMapper.dll" />
+      <MergeAsm Include="$(OutputPath)log4net.dll" />
+    </ItemGroup>
+    <PropertyGroup>
+      <MergedAssembly>$(ProjectDir)$(OutDir)winsw.exe</MergedAssembly>
+      <CertificatePfxFile>$(AssemblyOriginatorKeyFile)</CertificatePfxFile>
+    </PropertyGroup>
+    <!-- Locate SN.EXE in Windows SDK, use the first found one-->
+    <PropertyGroup>
+      <SnPathTmpFile>$(OutputPath)sn-path.txt</SnPathTmpFile>
+    </PropertyGroup>
+    <GetFrameworkSdkPath>
+      <Output TaskParameter="Path" PropertyName="WindowsSdkPath" />
+    </GetFrameworkSdkPath>
+    <Message Text="Using SDK from $(WindowsSdkPath)" Importance="high" />
+    <Exec Command="WHERE /r &quot;$(WindowsSdkPath.TrimEnd('\\'))&quot; sn &gt; $(SnPathTmpFile)" />
+    <ReadLinesFromFile File="$(SnPathTmpFile)">
+      <Output TaskParameter="Lines" PropertyName="SNPath" />
+    </ReadLinesFromFile>
+    <Delete Files="$(SnPathTmpFile)" />
+    <PropertyGroup>
+      <SNPath>$([System.Text.RegularExpressions.Regex]::Replace('$(SNPath)', ';.*', ''))</SNPath>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SNPath)')" Text="Cannot find SN.EXE utility in $(WindowsSdkPath)" />
+    <Message Text="Using SN.EXE utility from $(SNPath)" Importance="high" />
+    <!-- Merge and re-sign assemblies -->
+    <PropertyGroup>
+      <ILMergePath>$(SolutionDir)packages\ilmerge.2.14.1208\tools</ILMergePath>
+      <CertificateTmpPubFile>$(OutputPath)winsw_cert.pub</CertificateTmpPubFile>
+    </PropertyGroup>
+    <Message Text="Extracting public key from $(AssemblyOriginatorKeyFile)" />
+    <Exec Command="&quot;$(SNPath)&quot; -p &quot;$(AssemblyOriginatorKeyFile)&quot; &quot;$(CertificateTmpPubFile)&quot;" /> 
+    <Message Text="ILMerge @(MergeAsm) -&gt; $(MergedAssembly)" Importance="high" />
+    <ILMerge ToolPath="$(ILMergePath)" InputAssemblies="@(MergeAsm)" OutputFile="$(MergedAssembly)" TargetKind="SameAsPrimaryAssembly" KeyFile="$(CertificateTmpPubFile)" DelaySign="true" />
+    <Exec Command="&quot;$(SNPath)&quot; -R &quot;$(MergedAssembly)&quot; &quot;$(AssemblyOriginatorKeyFile)&quot;" />
+  </Target>
+</Project>

+ 0 - 0
winsw.xml → src/Core/ServiceWrapper/winsw.xml


+ 0 - 0
Download.cs → src/Core/WinSWCore/Download.cs


+ 0 - 0
DynamicProxy.cs → src/Core/WinSWCore/DynamicProxy.cs


+ 27 - 0
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
+        }
+    }
+}

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

+ 16 - 0
src/Core/WinSWCore/Extensions/ExtensionPointAttribute.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace winsw.Extensions
+{
+    /// <summary>
+    /// This attribute is used to identify extension points within the code
+    /// </summary>
+    /// <remarks>
+    /// Each extension point implements its own entry type.
+    /// </remarks>
+    class ExtensionPointAttribute
+    {
+    }
+}

+ 47 - 0
src/Core/WinSWCore/Extensions/IWinSWExtension.cs

@@ -0,0 +1,47 @@
+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 startup of the service before the child process.
+        /// </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);
+    }
+}

+ 45 - 0
src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Xml;
+using winsw.Util;
+
+namespace winsw.Extensions
+{
+    /// <summary>
+    /// Describes WinSW extensions in <see cref="IWinSWExtension"/>
+    /// </summary>
+    /// <remarks>
+    /// Any extension has its own descriptor instance.
+    /// </remarks>
+    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/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<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   
+    }
+}

+ 0 - 0
LogAppenders.cs → src/Core/WinSWCore/LogAppenders.cs


+ 6 - 6
Advapi32.cs → 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.
     /// </summary>
-    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,

+ 7 - 7
Kernel32.cs → src/Core/WinSWCore/Native/Kernel32.cs

@@ -1,18 +1,18 @@
 using System;
 using System.Runtime.InteropServices;
 
-namespace winsw
+namespace winsw.Native
 {
     /// <summary>
     /// kernel32.dll P/Invoke wrappers
     /// </summary>
-    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;

+ 0 - 0
PeriodicRollingCalendar.cs → src/Core/WinSWCore/PeriodicRollingCalendar.cs


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

+ 34 - 3
ServiceDescriptor.cs → 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
             }
         }
 
-        /// <summary>
-        /// Optional working directory.
-        /// </summary>
+        
         public string WorkingDirectory {
             get {
                 var wd = SingleElement("workingdirectory", true);
@@ -237,6 +237,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/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);
+    }
+}

+ 75 - 0
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
+    {
+        /// <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;
+        }
+    }
+}

+ 89 - 0
src/Core/WinSWCore/WinSWCore.csproj

@@ -0,0 +1,89 @@
+<?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>{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>winsw</RootNamespace>
+    <AssemblyName>WinSWCore</AssemblyName>
+    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <RestorePackages>true</RestorePackages>
+  </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>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>..\..\..\winsw_cert.pfx</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="log4net">
+      <HintPath>..\..\packages\log4net.2.0.3\lib\net20-full\log4net.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Management" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Download.cs" />
+    <Compile Include="DynamicProxy.cs" />
+    <Compile Include="Extensions\AbstractWinSWExtension.cs" />
+    <Compile Include="Extensions\ExtensionException.cs" />
+    <Compile Include="Extensions\ExtensionPointAttribute.cs" />
+    <Compile Include="Extensions\IWinSWExtension.cs" />
+    <Compile Include="Extensions\WinSWExtensionDescriptor.cs" />
+    <Compile Include="Extensions\WinSWExtensionManager.cs" />
+    <Compile Include="LogAppenders.cs" />
+    <Compile Include="Native\Advapi32.cs" />
+    <Compile Include="Native\Kernel32.cs" />
+    <Compile Include="PeriodicRollingCalendar.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ServiceDescriptor.cs" />
+    <Compile Include="Util\IEventWriter.cs" />
+    <Compile Include="Util\XmlHelper.cs" />
+    <Compile Include="WinSWException.cs" />
+    <Compile Include="Wmi.cs" />
+    <Compile Include="WmiSchema.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="..\..\..\winsw_cert.pfx" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>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}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
+  <!-- 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>

+ 17 - 0
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)
+        { }
+    }
+}

+ 0 - 0
Wmi.cs → src/Core/WinSWCore/Wmi.cs


+ 0 - 0
WmiSchema.cs → src/Core/WinSWCore/WmiSchema.cs


+ 4 - 0
src/Core/WinSWCore/packages.config

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="log4net" version="2.0.3" targetFramework="net20" />
+</packages>

+ 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\WinSWCore\WinSWCore.csproj">
+      <Project>{9d0c63e2-b6ff-4a85-bd36-b3e5d7f27d06}</Project>
+      <Name>WinSWCore</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>
+    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<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.Plugins.SharedDirectoryMapper.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);
+        }
+    }
+}

+ 0 - 0
Tests/winswTests/MainTest.cs → src/Test/winswTests/MainTest.cs


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

+ 0 - 0
Tests/winswTests/Properties/AssemblyInfo.cs → src/Test/winswTests/Properties/AssemblyInfo.cs


+ 0 - 0
Tests/winswTests/ServiceDescriptorTests.cs → src/Test/winswTests/ServiceDescriptorTests.cs


+ 0 - 0
Tests/winswTests/Util/CLITestHelper.cs → src/Test/winswTests/Util/CLITestHelper.cs


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

+ 1 - 0
Tests/winswTests/packages.config → src/Test/winswTests/packages.config

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="NUnit" version="2.6.4" targetFramework="net20" />
   <package id="JetBrains.Annotations" version="8.0.5.0" targetFramework="net20" />
 </packages>

+ 24 - 4
Tests/winswTests/winswTests.csproj → src/Test/winswTests/winswTests.csproj

@@ -17,6 +17,8 @@
     <UpgradeBackupLocation>
     </UpgradeBackupLocation>
     <OldToolsVersion>3.5</OldToolsVersion>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <RestorePackages>true</RestorePackages>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -35,14 +37,14 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
-  <ItemGroup>
+  <ItemGroup> 
     <Reference Include="JetBrains.Annotations, Version=8.0.5.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\..\packages\JetBrains.Annotations.8.0.5.0\lib\net20\JetBrains.Annotations.dll</HintPath>
     </Reference>
-    <Reference Include="nunit.framework, Version=2.6.2.12296, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
+    <Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\Lib\nunit.framework.dll</HintPath>
+      <HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Data" />
@@ -50,21 +52,39 @@
     <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>
-    <ProjectReference Include="..\..\winsw.csproj">
+    <ProjectReference Include="..\..\Core\ServiceWrapper\winsw.csproj">
       <Project>{0DE77F55-ADE5-43C1-999A-0BC81153B039}</Project>
       <Name>winsw</Name>
     </ProjectReference>
+    <ProjectReference Include="..\..\Core\WinSWCore\WinSWCore.csproj">
+      <Project>{9d0c63e2-b6ff-4a85-bd36-b3e5d7f27d06}</Project>
+      <Name>WinSWCore</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">
+    <PropertyGroup>
+      <ErrorText>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}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- 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">

+ 6 - 0
src/packages/repositories.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<repositories>
+  <repository path="..\Core\ServiceWrapper\packages.config" />
+  <repository path="..\Core\WinSWCore\packages.config" />
+  <repository path="..\Test\winswTests\packages.config" />
+</repositories>

+ 100 - 0
src/winsw.sln

@@ -0,0 +1,100 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.31101.0
+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}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6BDF4025-D46C-4C69-BDB2-5CE434C857AA}"
+	ProjectSection(SolutionItems) = preProject
+		.nuget\NuGet.Config = .nuget\NuGet.Config
+		.nuget\NuGet.exe = .nuget\NuGet.exe
+	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

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

+ 0 - 28
winsw.sln

@@ -1,28 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.31101.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Tests\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Win32 = Debug|Win32
-		Release|Win32 = Release|Win32
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{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|Win32.ActiveCfg = Release|Any CPU
-		{0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.Build.0 = Release|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|Win32.ActiveCfg = Release|Any CPU
-		{93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-EndGlobal