mirror of https://github.com/winsw/winsw
Prompt for elevation (#457)
* Prompt for elevation * Add error messages for interop errors * Assume process is elevated on Windows XP Co-authored-by: Oleg Nenashev <o.v.nenashev@gmail.com>pull/516/head v2.8.0
parent
a1d335bd60
commit
b8888c9a29
|
@ -49,6 +49,8 @@ Your renamed *WinSW.exe* binary also accepts the following commands:
|
||||||
* `Started` to indicate the service is currently running
|
* `Started` to indicate the service is currently running
|
||||||
* `Stopped` to indicate that the service is installed but not currently running.
|
* `Stopped` to indicate that the service is installed but not currently running.
|
||||||
|
|
||||||
|
Most commands require Administrator privileges to execute. Since v2.8, WinSW will prompt for UAC in non-elevated sessions.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
User documentation:
|
User documentation:
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.ServiceProcess;
|
using System.ServiceProcess;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -589,6 +591,26 @@ namespace winsw
|
||||||
args = args.GetRange(2, args.Count - 2);
|
args = args.GetRange(2, args.Count - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool elevated;
|
||||||
|
if (args[0] == "/elevated")
|
||||||
|
{
|
||||||
|
elevated = true;
|
||||||
|
|
||||||
|
_ = SigIntHelper.FreeConsole();
|
||||||
|
_ = SigIntHelper.AttachConsole(SigIntHelper.ATTACH_PARENT_PROCESS);
|
||||||
|
|
||||||
|
args = args.GetRange(1, args.Count - 1);
|
||||||
|
}
|
||||||
|
else if (Environment.OSVersion.Version.Major == 5)
|
||||||
|
{
|
||||||
|
// Windows XP
|
||||||
|
elevated = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elevated = IsProcessElevated();
|
||||||
|
}
|
||||||
|
|
||||||
switch (args[0].ToLower())
|
switch (args[0].ToLower())
|
||||||
{
|
{
|
||||||
case "install":
|
case "install":
|
||||||
|
@ -647,6 +669,12 @@ namespace winsw
|
||||||
|
|
||||||
void Install()
|
void Install()
|
||||||
{
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info("Installing the service with id '" + descriptor.Id + "'");
|
Log.Info("Installing the service with id '" + descriptor.Id + "'");
|
||||||
|
|
||||||
// Check if the service exists
|
// Check if the service exists
|
||||||
|
@ -738,6 +766,12 @@ namespace winsw
|
||||||
|
|
||||||
void Uninstall()
|
void Uninstall()
|
||||||
{
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info("Uninstalling the service with id '" + descriptor.Id + "'");
|
Log.Info("Uninstalling the service with id '" + descriptor.Id + "'");
|
||||||
if (s is null)
|
if (s is null)
|
||||||
{
|
{
|
||||||
|
@ -777,6 +811,12 @@ namespace winsw
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info("Starting the service with id '" + descriptor.Id + "'");
|
Log.Info("Starting the service with id '" + descriptor.Id + "'");
|
||||||
if (s is null)
|
if (s is null)
|
||||||
ThrowNoSuchService();
|
ThrowNoSuchService();
|
||||||
|
@ -800,6 +840,12 @@ namespace winsw
|
||||||
|
|
||||||
void Stop()
|
void Stop()
|
||||||
{
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info("Stopping the service with id '" + descriptor.Id + "'");
|
Log.Info("Stopping the service with id '" + descriptor.Id + "'");
|
||||||
if (s is null)
|
if (s is null)
|
||||||
ThrowNoSuchService();
|
ThrowNoSuchService();
|
||||||
|
@ -823,6 +869,12 @@ namespace winsw
|
||||||
|
|
||||||
void Restart()
|
void Restart()
|
||||||
{
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
|
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
|
||||||
if (s is null)
|
if (s is null)
|
||||||
ThrowNoSuchService();
|
ThrowNoSuchService();
|
||||||
|
@ -841,6 +893,11 @@ namespace winsw
|
||||||
|
|
||||||
void RestartSelf()
|
void RestartSelf()
|
||||||
{
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Access is denied.");
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
|
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
|
||||||
|
|
||||||
// run restart from another process group. see README.md for why this is useful.
|
// run restart from another process group. see README.md for why this is useful.
|
||||||
|
@ -865,6 +922,12 @@ namespace winsw
|
||||||
|
|
||||||
void Test()
|
void Test()
|
||||||
{
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WrapperService wsvc = new WrapperService(descriptor);
|
WrapperService wsvc = new WrapperService(descriptor);
|
||||||
wsvc.OnStart(args.ToArray());
|
wsvc.OnStart(args.ToArray());
|
||||||
Thread.Sleep(1000);
|
Thread.Sleep(1000);
|
||||||
|
@ -873,12 +936,52 @@ namespace winsw
|
||||||
|
|
||||||
void TestWait()
|
void TestWait()
|
||||||
{
|
{
|
||||||
|
if (!elevated)
|
||||||
|
{
|
||||||
|
Elevate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WrapperService wsvc = new WrapperService(descriptor);
|
WrapperService wsvc = new WrapperService(descriptor);
|
||||||
wsvc.OnStart(args.ToArray());
|
wsvc.OnStart(args.ToArray());
|
||||||
Console.WriteLine("Press any key to stop the service...");
|
Console.WriteLine("Press any key to stop the service...");
|
||||||
Console.Read();
|
Console.Read();
|
||||||
wsvc.OnStop();
|
wsvc.OnStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [DoesNotReturn]
|
||||||
|
void Elevate()
|
||||||
|
{
|
||||||
|
using Process current = Process.GetCurrentProcess();
|
||||||
|
|
||||||
|
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
UseShellExecute = true,
|
||||||
|
Verb = "runas",
|
||||||
|
FileName = current.MainModule.FileName,
|
||||||
|
#if NETCOREAPP
|
||||||
|
Arguments = "/elevated " + string.Join(' ', args),
|
||||||
|
#elif !NET20
|
||||||
|
Arguments = "/elevated " + string.Join(" ", args),
|
||||||
|
#else
|
||||||
|
Arguments = "/elevated " + string.Join(" ", args.ToArray()),
|
||||||
|
#endif
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using Process elevated = Process.Start(startInfo);
|
||||||
|
|
||||||
|
elevated.WaitForExit();
|
||||||
|
Environment.Exit(elevated.ExitCode);
|
||||||
|
}
|
||||||
|
catch (Win32Exception e) when (e.NativeErrorCode == Errors.ERROR_CANCELLED)
|
||||||
|
{
|
||||||
|
Log.Fatal(e.Message);
|
||||||
|
Environment.Exit(e.ErrorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitLoggers(ServiceDescriptor d, bool enableCLILogging)
|
private static void InitLoggers(ServiceDescriptor d, bool enableCLILogging)
|
||||||
|
@ -941,6 +1044,40 @@ namespace winsw
|
||||||
appenders.ToArray());
|
appenders.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static unsafe bool IsProcessElevated()
|
||||||
|
{
|
||||||
|
IntPtr process = Kernel32.GetCurrentProcess();
|
||||||
|
if (!Advapi32.OpenProcessToken(process, TokenAccessLevels.Read, out IntPtr token))
|
||||||
|
{
|
||||||
|
ThrowWin32Exception("Failed to open process token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Advapi32.GetTokenInformation(
|
||||||
|
token,
|
||||||
|
TOKEN_INFORMATION_CLASS.TokenElevation,
|
||||||
|
out TOKEN_ELEVATION elevation,
|
||||||
|
sizeof(TOKEN_ELEVATION),
|
||||||
|
out _))
|
||||||
|
{
|
||||||
|
ThrowWin32Exception("Failed to get token information");
|
||||||
|
}
|
||||||
|
|
||||||
|
return elevation.TokenIsElevated != 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ = Kernel32.CloseHandle(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ThrowWin32Exception(string message)
|
||||||
|
{
|
||||||
|
Win32Exception inner = new Win32Exception();
|
||||||
|
throw new Win32Exception(inner.NativeErrorCode, message + ' ' + inner.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string ReadPassword()
|
private static string ReadPassword()
|
||||||
{
|
{
|
||||||
StringBuilder buf = new StringBuilder();
|
StringBuilder buf = new StringBuilder();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<TargetFrameworks>net20;net40;net461;netcoreapp3.1</TargetFrameworks>
|
<TargetFrameworks>net20;net40;net461;netcoreapp3.1</TargetFrameworks>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
|
||||||
<AssemblyTitle>Windows Service Wrapper</AssemblyTitle>
|
<AssemblyTitle>Windows Service Wrapper</AssemblyTitle>
|
||||||
<Description>Allows arbitrary process to run as a Windows service by wrapping it.</Description>
|
<Description>Allows arbitrary process to run as a Windows service by wrapping it.</Description>
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
@ -321,6 +322,20 @@ namespace winsw.Native
|
||||||
|
|
||||||
[DllImport(Advapi32LibraryName, SetLastError = false)]
|
[DllImport(Advapi32LibraryName, SetLastError = false)]
|
||||||
internal static extern uint LsaNtStatusToWinError(uint status);
|
internal static extern uint LsaNtStatusToWinError(uint status);
|
||||||
|
|
||||||
|
[DllImport(Advapi32LibraryName, SetLastError = true)]
|
||||||
|
public static extern bool OpenProcessToken(
|
||||||
|
IntPtr ProcessHandle,
|
||||||
|
TokenAccessLevels DesiredAccess,
|
||||||
|
out IntPtr TokenHandle);
|
||||||
|
|
||||||
|
[DllImport(Advapi32LibraryName, SetLastError = true)]
|
||||||
|
public static extern bool GetTokenInformation(
|
||||||
|
IntPtr TokenHandle,
|
||||||
|
TOKEN_INFORMATION_CLASS TokenInformationClass,
|
||||||
|
out TOKEN_ELEVATION TokenInformation,
|
||||||
|
int TokenInformationLength,
|
||||||
|
out int ReturnLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx
|
||||||
|
@ -603,4 +618,14 @@ namespace winsw.Native
|
||||||
{
|
{
|
||||||
public string lpDescription;
|
public string lpDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum TOKEN_INFORMATION_CLASS
|
||||||
|
{
|
||||||
|
TokenElevation = 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct TOKEN_ELEVATION
|
||||||
|
{
|
||||||
|
public uint TokenIsElevated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace winsw.Native
|
||||||
|
{
|
||||||
|
public static class Errors
|
||||||
|
{
|
||||||
|
public const int ERROR_CANCELLED = 1223;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,12 @@ namespace winsw.Native
|
||||||
string? lpCurrentDirectory,
|
string? lpCurrentDirectory,
|
||||||
in STARTUPINFO lpStartupInfo,
|
in STARTUPINFO lpStartupInfo,
|
||||||
out PROCESS_INFORMATION lpProcessInformation);
|
out PROCESS_INFORMATION lpProcessInformation);
|
||||||
|
|
||||||
|
[DllImport(Kernel32LibraryName)]
|
||||||
|
public static extern IntPtr GetCurrentProcess();
|
||||||
|
|
||||||
|
[DllImport(Kernel32LibraryName)]
|
||||||
|
public static extern bool CloseHandle(IntPtr hObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
|
|
@ -9,13 +9,15 @@ namespace winsw.Util
|
||||||
{
|
{
|
||||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(SigIntHelper));
|
private static readonly ILog Logger = LogManager.GetLogger(typeof(SigIntHelper));
|
||||||
|
|
||||||
|
public const int ATTACH_PARENT_PROCESS = -1;
|
||||||
|
|
||||||
private const string Kernel32LibraryName = "kernel32.dll";
|
private const string Kernel32LibraryName = "kernel32.dll";
|
||||||
|
|
||||||
[DllImport(Kernel32LibraryName, SetLastError = true)]
|
[DllImport(Kernel32LibraryName, SetLastError = true)]
|
||||||
private static extern bool AttachConsole(uint dwProcessId);
|
public static extern bool AttachConsole(int dwProcessId);
|
||||||
|
|
||||||
[DllImport(Kernel32LibraryName, SetLastError = true)]
|
[DllImport(Kernel32LibraryName, SetLastError = true)]
|
||||||
private static extern bool FreeConsole();
|
public static extern bool FreeConsole();
|
||||||
|
|
||||||
[DllImport(Kernel32LibraryName)]
|
[DllImport(Kernel32LibraryName)]
|
||||||
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate? HandlerRoutine, bool Add);
|
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate? HandlerRoutine, bool Add);
|
||||||
|
@ -44,7 +46,7 @@ namespace winsw.Util
|
||||||
/// <returns>True if the process shut down successfully to the SIGINT, false if it did not.</returns>
|
/// <returns>True if the process shut down successfully to the SIGINT, false if it did not.</returns>
|
||||||
public static bool SendSIGINTToProcess(Process process, TimeSpan shutdownTimeout)
|
public static bool SendSIGINTToProcess(Process process, TimeSpan shutdownTimeout)
|
||||||
{
|
{
|
||||||
if (AttachConsole((uint)process.Id))
|
if (AttachConsole(process.Id))
|
||||||
{
|
{
|
||||||
// Disable Ctrl-C handling for our program
|
// Disable Ctrl-C handling for our program
|
||||||
_ = SetConsoleCtrlHandler(null, true);
|
_ = SetConsoleCtrlHandler(null, true);
|
||||||
|
|
Loading…
Reference in New Issue