mirror of https://github.com/winsw/winsw
parent
f5b44b958f
commit
a6ba41681d
|
@ -25,11 +25,6 @@ strategy:
|
|||
Release:
|
||||
BuildConfiguration: Release
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: Install .NET SDK
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: 6.x
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Build
|
||||
inputs:
|
||||
|
@ -37,9 +32,9 @@ steps:
|
|||
projects: src\WinSW.sln
|
||||
arguments: -c $(BuildConfiguration) -p:Version=$(BuildVersion)
|
||||
- script: |
|
||||
dotnet publish src\WinSW\WinSW.csproj -c $(BuildConfiguration) -f net6.0-windows -r win-x64 -p:Version=$(BuildVersion)
|
||||
dotnet publish src\WinSW\WinSW.csproj -c $(BuildConfiguration) -f net6.0-windows -r win-x86 -p:Version=$(BuildVersion)
|
||||
dotnet publish src\WinSW\WinSW.csproj -c $(BuildConfiguration) -f net6.0-windows -r win-arm64 -p:Version=$(BuildVersion)
|
||||
dotnet publish src\WinSW\WinSW.csproj -c $(BuildConfiguration) -f net7.0-windows -r win-x64 --sc -p:Version=$(BuildVersion)
|
||||
dotnet publish src\WinSW\WinSW.csproj -c $(BuildConfiguration) -f net7.0-windows -r win-x86 --sc -p:Version=$(BuildVersion)
|
||||
dotnet publish src\WinSW\WinSW.csproj -c $(BuildConfiguration) -f net7.0-windows -r win-arm64 --sc -p:Version=$(BuildVersion)
|
||||
displayName: Build
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Test
|
||||
|
@ -73,7 +68,7 @@ steps:
|
|||
|
||||
- publish: artifacts\publish\WinSW-arm64.exe
|
||||
artifact: WinSW-arm64.exe_$(BuildConfiguration)
|
||||
displayName: Publish .NET arm64 .exe
|
||||
displayName: Publish .NET Arm64 .exe
|
||||
|
||||
- publish: $(Build.ArtifactStagingDirectory)\WinSW.$(BuildVersion).nupkg
|
||||
artifact: WinSW.nupkg_$(BuildConfiguration)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyMetadata("IsTrimmable", "True")]
|
||||
[assembly: InternalsVisibleTo("WinSW")]
|
||||
[assembly: InternalsVisibleTo("WinSW.Tests")]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461;net6.0-windows</TargetFrameworks>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TargetFrameworks>net461;net7.0-windows</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
@ -15,13 +15,13 @@
|
|||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0-windows'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0-windows'">
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.AccessControl" Version="6.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="5.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0-windows'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net7.0-windows'">
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461;net6.0-windows</TargetFrameworks>
|
||||
<TargetFrameworks>net461;net7.0-windows</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -17,13 +17,18 @@ namespace WinSW.Tasks
|
|||
{
|
||||
using var module = ModuleDefinition.ReadModule(this.Path, new() { ReadWrite = true, ReadSymbols = true });
|
||||
|
||||
foreach (var t in module.CustomAttributeTypes())
|
||||
{
|
||||
this.WalkType(t);
|
||||
}
|
||||
|
||||
this.WalkType(module.EntryPoint.DeclaringType);
|
||||
|
||||
var types = module.Types;
|
||||
for (int i = types.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var type = types[i];
|
||||
if (type.FullName.Contains("WinSW.Plugins"))
|
||||
if (type.FullName.StartsWith("WinSW.Plugins"))
|
||||
{
|
||||
this.WalkType(type);
|
||||
}
|
||||
|
@ -64,6 +69,10 @@ namespace WinSW.Tasks
|
|||
this.WalkType(genericArg);
|
||||
}
|
||||
}
|
||||
else if (typeRef is IModifierType modifierType)
|
||||
{
|
||||
this.WalkType(modifierType.ModifierType);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.2.0" />
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.4.0" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ namespace WinSW.Tests
|
|||
|
||||
var result = Helper.ErrorTest(new[] { commandName });
|
||||
|
||||
Assert.Equal($"Unrecognized command or argument '{commandName}'\r\n\r\n", result.Error);
|
||||
Assert.Equal($"Unrecognized command or argument '{commandName}'.\r\n\r\n", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net471;net6.0-windows</TargetFrameworks>
|
||||
<TargetFrameworks>net471;net7.0-windows</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -12,15 +12,15 @@
|
|||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.0.226801" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Runtime.Utilities" Version="2.0.0-rc.20303.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0-windows'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net7.0-windows'">
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
</ItemGroup>
|
||||
|
@ -39,11 +39,11 @@
|
|||
|
||||
<Target Name="Copy" BeforeTargets="AfterBuild">
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0-windows'">
|
||||
<_FilesToCopy Include="$(ArtifactsBinDir)WinSW\$(Configuration)\net6.0-windows\WinSW.runtimeconfig*.json" />
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0-windows'">
|
||||
<_FilesToCopy Include="$(ArtifactsBinDir)WinSW\$(Configuration)\net7.0-windows\WinSW.runtimeconfig*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0-windows'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net7.0-windows'">
|
||||
<_FilesToCopy Include="$(ArtifactsBinDir)WinSW\$(Configuration)\net461\System.ValueTuple.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyMetadata("IsTrimmable", "True")]
|
||||
[assembly: InternalsVisibleTo("WinSW.Tests")]
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Invocation;
|
||||
|
||||
namespace WinSW
|
||||
{
|
||||
internal static class CommandExtensions
|
||||
{
|
||||
internal static void SetHandler<T>(this Command command, Action<T, InvocationContext> handle, Argument<T> symbol)
|
||||
{
|
||||
command.SetHandler(context =>
|
||||
{
|
||||
var value = context.ParseResult.GetValueForArgument(symbol);
|
||||
handle(value!, context);
|
||||
});
|
||||
}
|
||||
|
||||
internal static void SetHandler<T1, T2, T3>(this Command command, Action<T1, T2, T3, InvocationContext> handle, Argument<T1> symbol1, Option<T2> symbol2, Option<T3> symbol3)
|
||||
{
|
||||
command.SetHandler(context =>
|
||||
{
|
||||
var value1 = context.ParseResult.GetValueForArgument(symbol1);
|
||||
var value2 = context.ParseResult.GetValueForOption(symbol2);
|
||||
var value3 = context.ParseResult.GetValueForOption(symbol3);
|
||||
handle(value1!, value2!, value3!, context);
|
||||
});
|
||||
}
|
||||
|
||||
internal static void SetHandler<T1, T2, T3, T4>(this Command command, Action<T1, T2, T3, T4, InvocationContext> handle, Argument<T1> symbol1, Option<T2> symbol2, Option<T3> symbol3, Option<T4> symbol4)
|
||||
{
|
||||
command.SetHandler(context =>
|
||||
{
|
||||
var value1 = context.ParseResult.GetValueForArgument(symbol1);
|
||||
var value2 = context.ParseResult.GetValueForOption(symbol2);
|
||||
var value3 = context.ParseResult.GetValueForOption(symbol3);
|
||||
var value4 = context.ParseResult.GetValueForOption(symbol4);
|
||||
handle(value1!, value2!, value3!, value4!, context);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ using System.Reflection;
|
|||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
using log4net;
|
||||
using log4net.Appender;
|
||||
using log4net.Config;
|
||||
|
@ -107,9 +106,272 @@ namespace WinSW
|
|||
elevated = IsProcessElevated();
|
||||
}
|
||||
|
||||
var root = new RootCommand("A wrapper binary that can be used to host executables as Windows services. https://github.com/winsw/winsw")
|
||||
var serviceConfig = new Argument<string?>("path-to-config")
|
||||
{
|
||||
Handler = CommandHandler.Create((string? pathToConfig) =>
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
IsHidden = true,
|
||||
};
|
||||
|
||||
var root = new RootCommand("A wrapper binary that can be used to host executables as Windows services. https://github.com/winsw/winsw");
|
||||
|
||||
using (var identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
var principal = new WindowsPrincipal(identity);
|
||||
if (principal.IsInRole(new SecurityIdentifier(WellKnownSidType.ServiceSid, null)) ||
|
||||
principal.IsInRole(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null)) ||
|
||||
principal.IsInRole(new SecurityIdentifier(WellKnownSidType.LocalServiceSid, null)) ||
|
||||
principal.IsInRole(new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null)))
|
||||
{
|
||||
root.Add(serviceConfig);
|
||||
}
|
||||
}
|
||||
|
||||
root.SetHandler(Run, serviceConfig);
|
||||
|
||||
var config = new Argument<string?>("path-to-config", "The path to the configuration file.")
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
};
|
||||
|
||||
var noElevate = new Option<bool>("--no-elevate", "Doesn't automatically trigger a UAC prompt.");
|
||||
|
||||
{
|
||||
var username = new Option<string?>(new[] { "--username", "--user" }, "Specifies the user name of the service account.");
|
||||
var password = new Option<string?>(new[] { "--password", "--pass" }, "Specifies the password of the service account.");
|
||||
|
||||
var install = new Command("install", "Installs the service.")
|
||||
{
|
||||
config,
|
||||
noElevate,
|
||||
username,
|
||||
password,
|
||||
};
|
||||
install.SetHandler(Install, config, noElevate, username, password);
|
||||
|
||||
root.Add(install);
|
||||
}
|
||||
|
||||
{
|
||||
var uninstall = new Command("uninstall", "Uninstalls the service.")
|
||||
{
|
||||
config,
|
||||
noElevate,
|
||||
};
|
||||
uninstall.SetHandler(Uninstall, config, noElevate);
|
||||
|
||||
root.Add(uninstall);
|
||||
}
|
||||
|
||||
{
|
||||
var noWait = new Option<bool>("--no-wait", "Doesn't wait for the service to actually start.");
|
||||
|
||||
var start = new Command("start", "Starts the service.")
|
||||
{
|
||||
config,
|
||||
noElevate,
|
||||
noWait,
|
||||
};
|
||||
start.SetHandler(Start, config, noElevate, noWait);
|
||||
|
||||
root.Add(start);
|
||||
}
|
||||
|
||||
{
|
||||
var noWait = new Option<bool>("--no-wait", "Doesn't wait for the service to actually stop.");
|
||||
var force = new Option<bool>("--force", "Stops the service even if it has started dependent services.");
|
||||
|
||||
var stop = new Command("stop", "Stops the service.")
|
||||
{
|
||||
config,
|
||||
noElevate,
|
||||
noWait,
|
||||
force,
|
||||
};
|
||||
stop.SetHandler(Stop, config, noElevate, noWait, force);
|
||||
|
||||
root.Add(stop);
|
||||
}
|
||||
|
||||
{
|
||||
var force = new Option<bool>("--force", "Restarts the service even if it has started dependent services.");
|
||||
|
||||
var restart = new Command("restart", "Stops and then starts the service.")
|
||||
{
|
||||
config,
|
||||
noElevate,
|
||||
force,
|
||||
};
|
||||
restart.SetHandler(Restart, config, noElevate, force);
|
||||
|
||||
root.Add(restart);
|
||||
}
|
||||
|
||||
{
|
||||
var restartSelf = new Command("restart!", "self-restart (can be called from child processes)")
|
||||
{
|
||||
config,
|
||||
};
|
||||
restartSelf.SetHandler(RestartSelf, config);
|
||||
|
||||
root.Add(restartSelf);
|
||||
}
|
||||
|
||||
{
|
||||
var status = new Command("status", "Checks the status of the service.")
|
||||
{
|
||||
config,
|
||||
};
|
||||
status.SetHandler(Status, config);
|
||||
|
||||
root.Add(status);
|
||||
}
|
||||
|
||||
{
|
||||
var refresh = new Command("refresh", "Refreshes the service properties without reinstallation.")
|
||||
{
|
||||
config,
|
||||
noElevate,
|
||||
};
|
||||
refresh.SetHandler(Refresh, config, noElevate);
|
||||
|
||||
root.Add(refresh);
|
||||
}
|
||||
|
||||
{
|
||||
var output = new Option<string>(new[] { "--output", "-o" })
|
||||
{
|
||||
IsRequired = true,
|
||||
};
|
||||
|
||||
var manufacturer = new Option<string>("--manufacturer")
|
||||
{
|
||||
IsRequired = true,
|
||||
};
|
||||
manufacturer.AddValidator(result =>
|
||||
{
|
||||
const int minLength = 12;
|
||||
const int maxLength = 15;
|
||||
|
||||
string token = result.Tokens.Single().Value;
|
||||
int length = token.Length;
|
||||
result.ErrorMessage =
|
||||
length < minLength ? $"The length of argument '{token}' must be greater than or equal to {minLength}." :
|
||||
length > maxLength ? $"The length of argument '{token}' must be less than or equal to {maxLength}." :
|
||||
null;
|
||||
});
|
||||
|
||||
var customize = new Command("customize", "Customizes the wrapper executable.")
|
||||
{
|
||||
output,
|
||||
manufacturer,
|
||||
};
|
||||
customize.SetHandler(Customize, output, manufacturer);
|
||||
|
||||
root.Add(customize);
|
||||
}
|
||||
|
||||
{
|
||||
var dev = new Command("dev", "Experimental commands.");
|
||||
|
||||
root.Add(dev);
|
||||
|
||||
{
|
||||
var all = new Option<bool>(new[] { "--all", "-a" });
|
||||
|
||||
var ps = new Command("ps", "Draws the process tree associated with the service.")
|
||||
{
|
||||
config,
|
||||
all,
|
||||
};
|
||||
ps.SetHandler(DevPs, config, all);
|
||||
|
||||
dev.Add(ps);
|
||||
}
|
||||
|
||||
{
|
||||
var kill = new Command("kill", "Terminates the service if it has stopped responding.")
|
||||
{
|
||||
config,
|
||||
noElevate,
|
||||
};
|
||||
kill.SetHandler(DevKill, config, noElevate);
|
||||
|
||||
dev.Add(kill);
|
||||
}
|
||||
|
||||
{
|
||||
var list = new Command("list", "Lists services managed by the current executable.");
|
||||
list.SetHandler(DevList);
|
||||
|
||||
dev.Add(list);
|
||||
}
|
||||
}
|
||||
|
||||
return new CommandLineBuilder(root)
|
||||
.UseVersionOption()
|
||||
.UseHelp()
|
||||
.RegisterWithDotnetSuggest()
|
||||
.UseTypoCorrections()
|
||||
.UseParseErrorReporting()
|
||||
.UseExceptionHandler(TestExceptionHandler ?? OnException)
|
||||
.CancelOnProcessTermination()
|
||||
.Build()
|
||||
.Invoke(args);
|
||||
|
||||
static void OnException(Exception exception, InvocationContext context)
|
||||
{
|
||||
switch (exception)
|
||||
{
|
||||
case InvalidDataException e:
|
||||
{
|
||||
string message = "The configuration file could not be loaded. " + e.Message;
|
||||
Log.Fatal(message, e);
|
||||
context.ExitCode = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case OperationCanceledException e:
|
||||
{
|
||||
Debug.Assert(e.CancellationToken == context.GetCancellationToken());
|
||||
Log.Fatal(e.Message);
|
||||
context.ExitCode = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case CommandException e:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message);
|
||||
context.ExitCode = e.InnerException is Win32Exception inner ? inner.NativeErrorCode : -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case InvalidOperationException e when e.InnerException is Win32Exception inner:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message);
|
||||
context.ExitCode = inner.NativeErrorCode;
|
||||
break;
|
||||
}
|
||||
|
||||
case Win32Exception e:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message, e);
|
||||
context.ExitCode = e.NativeErrorCode;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
Log.Fatal("Unhandled exception", exception);
|
||||
context.ExitCode = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Run(string? pathToConfig)
|
||||
{
|
||||
XmlServiceConfig config = null!;
|
||||
try
|
||||
|
@ -134,270 +396,6 @@ namespace WinSW
|
|||
{
|
||||
// handled in OnStart
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
using (var identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
var principal = new WindowsPrincipal(identity);
|
||||
if (principal.IsInRole(new SecurityIdentifier(WellKnownSidType.ServiceSid, null)) ||
|
||||
principal.IsInRole(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null)) ||
|
||||
principal.IsInRole(new SecurityIdentifier(WellKnownSidType.LocalServiceSid, null)) ||
|
||||
principal.IsInRole(new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null)))
|
||||
{
|
||||
root.Add(new Argument<string?>("path-to-config")
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
IsHidden = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Argument<string?>("path-to-config", "The path to the configuration file.")
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
};
|
||||
|
||||
var noElevate = new Option("--no-elevate", "Doesn't automatically trigger a UAC prompt.");
|
||||
|
||||
{
|
||||
var install = new Command("install", "Installs the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool, string?, string?>(Install),
|
||||
};
|
||||
|
||||
install.Add(config);
|
||||
install.Add(noElevate);
|
||||
install.Add(new Option<string?>(new[] { "--username", "--user" }, "Specifies the user name of the service account."));
|
||||
install.Add(new Option<string?>(new[] { "--password", "--pass" }, "Specifies the password of the service account."));
|
||||
|
||||
root.Add(install);
|
||||
}
|
||||
|
||||
{
|
||||
var uninstall = new Command("uninstall", "Uninstalls the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool>(Uninstall),
|
||||
};
|
||||
|
||||
uninstall.Add(config);
|
||||
uninstall.Add(noElevate);
|
||||
|
||||
root.Add(uninstall);
|
||||
}
|
||||
|
||||
{
|
||||
var start = new Command("start", "Starts the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool, bool, CancellationToken>(Start),
|
||||
};
|
||||
|
||||
start.Add(config);
|
||||
start.Add(noElevate);
|
||||
start.Add(new Option("--no-wait", "Doesn't wait for the service to actually start."));
|
||||
|
||||
root.Add(start);
|
||||
}
|
||||
|
||||
{
|
||||
var stop = new Command("stop", "Stops the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool, bool, bool, CancellationToken>(Stop),
|
||||
};
|
||||
|
||||
stop.Add(config);
|
||||
stop.Add(noElevate);
|
||||
stop.Add(new Option("--no-wait", "Doesn't wait for the service to actually stop."));
|
||||
stop.Add(new Option("--force", "Stops the service even if it has started dependent services."));
|
||||
|
||||
root.Add(stop);
|
||||
}
|
||||
|
||||
{
|
||||
var restart = new Command("restart", "Stops and then starts the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool, bool, CancellationToken>(Restart),
|
||||
};
|
||||
|
||||
restart.Add(config);
|
||||
restart.Add(noElevate);
|
||||
restart.Add(new Option("--force", "Restarts the service even if it has started dependent services."));
|
||||
|
||||
root.Add(restart);
|
||||
}
|
||||
|
||||
{
|
||||
var restartSelf = new Command("restart!", "self-restart (can be called from child processes)")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?>(RestartSelf),
|
||||
};
|
||||
|
||||
restartSelf.Add(config);
|
||||
|
||||
root.Add(restartSelf);
|
||||
}
|
||||
|
||||
{
|
||||
var status = new Command("status", "Checks the status of the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?>(Status),
|
||||
};
|
||||
|
||||
status.Add(config);
|
||||
|
||||
root.Add(status);
|
||||
}
|
||||
|
||||
{
|
||||
var refresh = new Command("refresh", "Refreshes the service properties without reinstallation.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool>(Refresh),
|
||||
};
|
||||
|
||||
refresh.Add(config);
|
||||
refresh.Add(noElevate);
|
||||
|
||||
root.Add(refresh);
|
||||
}
|
||||
|
||||
{
|
||||
var customize = new Command("customize", "Customizes the wrapper executable.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string, string>(Customize),
|
||||
};
|
||||
|
||||
customize.Add(new Option<string>(new[] { "--output", "-o" })
|
||||
{
|
||||
Required = true,
|
||||
});
|
||||
|
||||
var manufacturer = new Option<string>("--manufacturer")
|
||||
{
|
||||
Required = true,
|
||||
};
|
||||
manufacturer.Argument.AddValidator(argument =>
|
||||
{
|
||||
const int minLength = 12;
|
||||
const int maxLength = 15;
|
||||
|
||||
string token = argument.Tokens.Single().Value;
|
||||
int length = token.Length;
|
||||
return
|
||||
length < minLength ? $"The length of argument '{token}' must be greater than or equal to {minLength}." :
|
||||
length > maxLength ? $"The length of argument '{token}' must be less than or equal to {maxLength}." :
|
||||
null;
|
||||
});
|
||||
|
||||
customize.Add(manufacturer);
|
||||
|
||||
root.Add(customize);
|
||||
}
|
||||
|
||||
{
|
||||
var dev = new Command("dev", "Experimental commands.");
|
||||
|
||||
root.Add(dev);
|
||||
|
||||
{
|
||||
var ps = new Command("ps", "Draws the process tree associated with the service.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool>(DevPs),
|
||||
};
|
||||
|
||||
ps.Add(config);
|
||||
|
||||
ps.Add(new Option(new[] { "--all", "-a" }));
|
||||
|
||||
dev.Add(ps);
|
||||
}
|
||||
|
||||
{
|
||||
var kill = new Command("kill", "Terminates the service if it has stopped responding.")
|
||||
{
|
||||
Handler = CommandHandler.Create<string?, bool>(DevKill),
|
||||
};
|
||||
|
||||
kill.Add(config);
|
||||
kill.Add(noElevate);
|
||||
|
||||
dev.Add(kill);
|
||||
}
|
||||
|
||||
{
|
||||
var list = new Command("list", "Lists services managed by the current executable.")
|
||||
{
|
||||
Handler = CommandHandler.Create(DevList),
|
||||
};
|
||||
|
||||
dev.Add(list);
|
||||
}
|
||||
}
|
||||
|
||||
return new CommandLineBuilder(root)
|
||||
.UseVersionOption()
|
||||
.UseHelp()
|
||||
.RegisterWithDotnetSuggest()
|
||||
.UseTypoCorrections()
|
||||
.UseParseErrorReporting()
|
||||
.UseExceptionHandler(TestExceptionHandler ?? OnException)
|
||||
.CancelOnProcessTermination()
|
||||
.Build()
|
||||
.Invoke(args);
|
||||
|
||||
static void OnException(Exception exception, InvocationContext context)
|
||||
{
|
||||
Debug.Assert(exception is TargetInvocationException);
|
||||
Debug.Assert(exception.InnerException != null);
|
||||
exception = exception.InnerException!;
|
||||
switch (exception)
|
||||
{
|
||||
case InvalidDataException e:
|
||||
{
|
||||
string message = "The configuration file could not be loaded. " + e.Message;
|
||||
Log.Fatal(message, e);
|
||||
context.ResultCode = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case OperationCanceledException e:
|
||||
{
|
||||
Debug.Assert(e.CancellationToken == context.GetCancellationToken());
|
||||
Log.Fatal(e.Message);
|
||||
context.ResultCode = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case CommandException e:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message);
|
||||
context.ResultCode = e.InnerException is Win32Exception inner ? inner.NativeErrorCode : -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case InvalidOperationException e when e.InnerException is Win32Exception inner:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message);
|
||||
context.ResultCode = inner.NativeErrorCode;
|
||||
break;
|
||||
}
|
||||
|
||||
case Win32Exception e:
|
||||
{
|
||||
string message = e.Message;
|
||||
Log.Fatal(message, e);
|
||||
context.ResultCode = e.NativeErrorCode;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
Log.Fatal("Unhandled exception", exception);
|
||||
context.ResultCode = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Install(string? pathToConfig, bool noElevate, string? username, string? password)
|
||||
|
@ -557,7 +555,7 @@ namespace WinSW
|
|||
}
|
||||
}
|
||||
|
||||
void Start(string? pathToConfig, bool noElevate, bool noWait, CancellationToken ct)
|
||||
void Start(string? pathToConfig, bool noElevate, bool noWait, InvocationContext context)
|
||||
{
|
||||
var config = LoadConfigAndInitLoggers(pathToConfig, true);
|
||||
|
||||
|
@ -580,6 +578,7 @@ namespace WinSW
|
|||
{
|
||||
try
|
||||
{
|
||||
var ct = context.GetCancellationToken();
|
||||
svc.WaitForStatus(ServiceControllerStatus.Running, ServiceControllerStatus.StartPending, ct);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
|
@ -602,7 +601,7 @@ namespace WinSW
|
|||
}
|
||||
}
|
||||
|
||||
void Stop(string? pathToConfig, bool noElevate, bool noWait, bool force, CancellationToken ct)
|
||||
void Stop(string? pathToConfig, bool noElevate, bool noWait, bool force, InvocationContext context)
|
||||
{
|
||||
var config = LoadConfigAndInitLoggers(pathToConfig, true);
|
||||
|
||||
|
@ -633,6 +632,7 @@ namespace WinSW
|
|||
{
|
||||
try
|
||||
{
|
||||
var ct = context.GetCancellationToken();
|
||||
svc.WaitForStatus(ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending, ct);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
|
@ -655,7 +655,7 @@ namespace WinSW
|
|||
}
|
||||
}
|
||||
|
||||
void Restart(string? pathToConfig, bool noElevate, bool force, CancellationToken ct)
|
||||
void Restart(string? pathToConfig, bool noElevate, bool force, InvocationContext context)
|
||||
{
|
||||
var config = LoadConfigAndInitLoggers(pathToConfig, true);
|
||||
|
||||
|
@ -688,6 +688,7 @@ namespace WinSW
|
|||
|
||||
try
|
||||
{
|
||||
var ct = context.GetCancellationToken();
|
||||
svc.WaitForStatus(ServiceControllerStatus.Stopped, ServiceControllerStatus.StopPending, ct);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
|
@ -710,6 +711,7 @@ namespace WinSW
|
|||
|
||||
try
|
||||
{
|
||||
var ct = context.GetCancellationToken();
|
||||
svc.WaitForStatus(ServiceControllerStatus.Running, ServiceControllerStatus.StartPending, ct);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
|
@ -763,7 +765,7 @@ namespace WinSW
|
|||
_ = HandleApis.CloseHandle(processInfo.ThreadHandle);
|
||||
}
|
||||
|
||||
static int Status(string? pathToConfig)
|
||||
static void Status(string? pathToConfig, InvocationContext context)
|
||||
{
|
||||
var config = LoadConfigAndInitLoggers(pathToConfig, true);
|
||||
|
||||
|
@ -781,7 +783,7 @@ namespace WinSW
|
|||
_ => "Inactive (stopped)"
|
||||
});
|
||||
|
||||
return svc.Status switch
|
||||
context.ExitCode = svc.Status switch
|
||||
{
|
||||
ServiceControllerStatus.Stopped => 0,
|
||||
_ => 1
|
||||
|
@ -791,7 +793,7 @@ namespace WinSW
|
|||
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
{
|
||||
Console.WriteLine("NonExistent");
|
||||
return Errors.ERROR_SERVICE_DOES_NOT_EXIST;
|
||||
context.ExitCode = Errors.ERROR_SERVICE_DOES_NOT_EXIST;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net461;net6.0-windows</TargetFrameworks>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TargetFrameworks>net461;net7.0-windows</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
|
||||
<AssemblyTitle>Windows Service Wrapper</AssemblyTitle>
|
||||
<Description>Allows arbitrary process to run as a Windows service by wrapping it.</Description>
|
||||
|
@ -15,19 +14,27 @@
|
|||
<Copyright>Copyright (c) 2008-2020 Kohsuke Kawaguchi, Sun Microsystems, Inc., CloudBees, Inc., Oleg Nenashev and other contributors</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net6.0-windows' AND '$(RuntimeIdentifier)' != ''">
|
||||
<PropertyGroup>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>partial</TrimMode>
|
||||
<DebuggerSupport>false</DebuggerSupport>
|
||||
<NullabilityInfoContextSupport>false</NullabilityInfoContextSupport>
|
||||
<_AggressiveAttributeTrimming>true</_AggressiveAttributeTrimming>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net7.0-windows' AND '$(RuntimeIdentifier)' != ''">
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' != 'net6.0-windows'">
|
||||
<PropertyGroup Condition="'$(TargetFramework)' != 'net7.0-windows'">
|
||||
<ILMergeVersion>3.0.41</ILMergeVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20303.1" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0-windows'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net7.0-windows'">
|
||||
<PackageReference Include="ilmerge" Version="$(ILMergeVersion)" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
</ItemGroup>
|
||||
|
@ -37,11 +44,11 @@
|
|||
<ProjectReference Include="..\WinSW.Plugins\WinSW.Plugins.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0-windows'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net7.0-windows'">
|
||||
<ProjectReference Include="..\WinSW.Tasks\WinSW.Tasks.csproj" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PublishCoreExe" AfterTargets="Publish" Condition="'$(TargetFramework)' == 'net6.0-windows'">
|
||||
<Target Name="PublishCoreExe" AfterTargets="Publish" Condition="'$(TargetFramework)' == 'net7.0-windows'">
|
||||
|
||||
<MakeDir Directories="$(ArtifactsPublishDir)" />
|
||||
<Copy SourceFiles="$(PublishDir)$(TargetName).exe" DestinationFiles="$(ArtifactsPublishDir)WinSW-$(PlatformTarget).exe" />
|
||||
|
@ -49,7 +56,7 @@
|
|||
</Target>
|
||||
|
||||
<!-- Merge plugins and other DLLs into the executable -->
|
||||
<Target Name="Merge" BeforeTargets="AfterBuild" Condition="'$(TargetFramework)' != 'net6.0-windows'">
|
||||
<Target Name="Merge" BeforeTargets="AfterBuild" Condition="'$(TargetFramework)' != 'net7.0-windows'">
|
||||
|
||||
<PropertyGroup>
|
||||
<InputAssemblies>"$(OutDir)$(TargetFileName)"</InputAssemblies>
|
||||
|
@ -77,7 +84,7 @@
|
|||
</Target>
|
||||
|
||||
<UsingTask TaskName="WinSW.Tasks.Trim" AssemblyFile="$(ArtifactsBinDir)WinSW.Tasks\$(Configuration)\net461\WinSW.Tasks.dll" />
|
||||
<Target Name="Trim" AfterTargets="Merge" Condition="'$(TargetFramework)' != 'net6.0-windows'">
|
||||
<Target Name="Trim" AfterTargets="Merge" Condition="'$(TargetFramework)' != 'net7.0-windows'">
|
||||
<Trim Path="$(ArtifactsPublishDir)WinSW-$(TargetFramework).exe" />
|
||||
</Target>
|
||||
|
||||
|
|
Loading…
Reference in New Issue