mirror of https://github.com/winsw/winsw
Enhance console prompting
parent
6d5dcfa70c
commit
576cf6be55
|
@ -1,6 +1,8 @@
|
||||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using static WinSW.Native.Libraries;
|
||||||
|
|
||||||
namespace WinSW.Native
|
namespace WinSW.Native
|
||||||
{
|
{
|
||||||
|
@ -10,24 +12,41 @@ namespace WinSW.Native
|
||||||
|
|
||||||
internal const uint CP_UTF8 = 65001;
|
internal const uint CP_UTF8 = 65001;
|
||||||
|
|
||||||
[DllImport(Libraries.Kernel32, SetLastError = true)]
|
internal const uint ENABLE_PROCESSED_INPUT = 0x0001;
|
||||||
|
internal const uint ENABLE_LINE_INPUT = 0x0002;
|
||||||
|
internal const uint ENABLE_ECHO_INPUT = 0x0004;
|
||||||
|
internal const uint ENABLE_MOUSE_INPUT = 0x0010;
|
||||||
|
|
||||||
|
[DllImport(Kernel32, SetLastError = true)]
|
||||||
internal static extern bool AllocConsole();
|
internal static extern bool AllocConsole();
|
||||||
|
|
||||||
[DllImport(Libraries.Kernel32, SetLastError = true)]
|
[DllImport(Kernel32, SetLastError = true)]
|
||||||
internal static extern bool AttachConsole(int processId);
|
internal static extern bool AttachConsole(int processId);
|
||||||
|
|
||||||
[DllImport(Libraries.Kernel32)]
|
[DllImport(Kernel32)]
|
||||||
internal static extern bool FreeConsole();
|
internal static extern bool FreeConsole();
|
||||||
|
|
||||||
[DllImport(Libraries.Kernel32)]
|
[DllImport(Kernel32)]
|
||||||
internal static extern bool GenerateConsoleCtrlEvent(CtrlEvents ctrlEvent, uint processGroupId);
|
internal static extern bool GenerateConsoleCtrlEvent(CtrlEvents ctrlEvent, uint processGroupId);
|
||||||
|
|
||||||
[DllImport(Libraries.Kernel32)]
|
[DllImport(Kernel32, SetLastError = true)]
|
||||||
|
internal static extern bool GetConsoleMode(IntPtr consoleHandle, out uint mode);
|
||||||
|
|
||||||
|
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
internal static extern bool ReadConsoleW(IntPtr consoleInput, out char buffer, int numberOfCharsToRead, out int numberOfCharsRead, IntPtr inputControl);
|
||||||
|
|
||||||
|
[DllImport(Kernel32)]
|
||||||
internal static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandlerRoutine? handlerRoutine, bool add);
|
internal static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandlerRoutine? handlerRoutine, bool add);
|
||||||
|
|
||||||
[DllImport(Libraries.Kernel32)]
|
[DllImport(Kernel32, SetLastError = true)]
|
||||||
|
internal static extern bool SetConsoleMode(IntPtr consoleHandle, uint mode);
|
||||||
|
|
||||||
|
[DllImport(Kernel32)]
|
||||||
internal static extern bool SetConsoleOutputCP(uint codePageID);
|
internal static extern bool SetConsoleOutputCP(uint codePageID);
|
||||||
|
|
||||||
|
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
internal static extern bool WriteConsoleW(IntPtr consoleOutput, string buffer, int numberOfCharsToWrite, out int numberOfCharsWritten, IntPtr reserved);
|
||||||
|
|
||||||
internal delegate bool ConsoleCtrlHandlerRoutine(CtrlEvents ctrlType);
|
internal delegate bool ConsoleCtrlHandlerRoutine(CtrlEvents ctrlType);
|
||||||
|
|
||||||
internal enum CtrlEvents : uint
|
internal enum CtrlEvents : uint
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using static WinSW.Native.ConsoleApis;
|
||||||
|
|
||||||
|
namespace WinSW.Native
|
||||||
|
{
|
||||||
|
internal static class ConsoleEx
|
||||||
|
{
|
||||||
|
internal static Handle OpenConsoleInput()
|
||||||
|
{
|
||||||
|
return FileApis.CreateFileW(
|
||||||
|
"CONIN$",
|
||||||
|
FileApis.GenericAccess.Read | FileApis.GenericAccess.Write,
|
||||||
|
FileShare.Read | FileShare.Write,
|
||||||
|
IntPtr.Zero,
|
||||||
|
FileMode.Open,
|
||||||
|
0,
|
||||||
|
IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Handle OpenConsoleOutput()
|
||||||
|
{
|
||||||
|
return FileApis.CreateFileW(
|
||||||
|
"CONOUT$",
|
||||||
|
FileApis.GenericAccess.Write,
|
||||||
|
FileShare.Write,
|
||||||
|
IntPtr.Zero,
|
||||||
|
FileMode.Open,
|
||||||
|
0,
|
||||||
|
IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <exception cref="CommandException" />
|
||||||
|
internal static string ReadPassword()
|
||||||
|
{
|
||||||
|
using var consoleInput = OpenConsoleInput();
|
||||||
|
using var consoleOutput = OpenConsoleOutput();
|
||||||
|
|
||||||
|
if (!GetConsoleMode(consoleInput, out uint mode))
|
||||||
|
{
|
||||||
|
Throw.Command.Win32Exception("Failed to get console mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint newMode = mode;
|
||||||
|
newMode &= ~ENABLE_PROCESSED_INPUT;
|
||||||
|
newMode &= ~ENABLE_LINE_INPUT;
|
||||||
|
newMode &= ~ENABLE_ECHO_INPUT;
|
||||||
|
newMode &= ~ENABLE_MOUSE_INPUT;
|
||||||
|
|
||||||
|
if (newMode != mode)
|
||||||
|
{
|
||||||
|
if (!SetConsoleMode(consoleInput, newMode))
|
||||||
|
{
|
||||||
|
Throw.Command.Win32Exception("Failed to set console mode.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (!ReadConsoleW(consoleInput, out char key, 1, out _, IntPtr.Zero))
|
||||||
|
{
|
||||||
|
Throw.Command.Win32Exception("Failed to read console.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == (char)3)
|
||||||
|
{
|
||||||
|
// Ctrl+C
|
||||||
|
Write(consoleOutput, Environment.NewLine);
|
||||||
|
Throw.Command.Win32Exception(Errors.ERROR_CANCELLED);
|
||||||
|
}
|
||||||
|
else if (key == '\r')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (key == '\b')
|
||||||
|
{
|
||||||
|
if (buffer.Length > 0)
|
||||||
|
{
|
||||||
|
buffer.Remove(buffer.Length - 1, 1);
|
||||||
|
Write(consoleOutput, "\b \b");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer.Append(key);
|
||||||
|
Write(consoleOutput, "*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.ToString();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (newMode != mode)
|
||||||
|
{
|
||||||
|
if (!SetConsoleMode(consoleInput, mode))
|
||||||
|
{
|
||||||
|
Throw.Command.Win32Exception("Failed to set console mode.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Write(Handle consoleOutput, string value)
|
||||||
|
{
|
||||||
|
if (!WriteConsoleW(consoleOutput, value, value.Length, out _, IntPtr.Zero))
|
||||||
|
{
|
||||||
|
Throw.Command.Win32Exception("Failed to write console.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,24 @@ namespace WinSW.Native
|
||||||
{
|
{
|
||||||
internal static class Credentials
|
internal static class Credentials
|
||||||
{
|
{
|
||||||
internal static void PropmtForCredentialsDialog(ref string? userName, ref string? password, string caption, string message)
|
internal static void PromptForCredentialsConsole(ref string? userName, ref string? password)
|
||||||
|
{
|
||||||
|
using var consoleOutput = ConsoleEx.OpenConsoleOutput();
|
||||||
|
|
||||||
|
if (userName is null)
|
||||||
|
{
|
||||||
|
ConsoleEx.Write(consoleOutput, "Username: ");
|
||||||
|
userName = Console.ReadLine()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password is null && !Security.IsSpecialAccount(userName))
|
||||||
|
{
|
||||||
|
ConsoleEx.Write(consoleOutput, "Password: ");
|
||||||
|
password = ConsoleEx.ReadPassword();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void PromptForCredentialsDialog(ref string? userName, ref string? password, string caption, string message)
|
||||||
{
|
{
|
||||||
userName ??= string.Empty;
|
userName ??= string.Empty;
|
||||||
password ??= string.Empty;
|
password ??= string.Empty;
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace WinSW.Native
|
||||||
|
{
|
||||||
|
internal static class FileApis
|
||||||
|
{
|
||||||
|
[DllImport(Libraries.Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
internal static extern Handle CreateFileW(
|
||||||
|
string fileName,
|
||||||
|
GenericAccess desiredAccess,
|
||||||
|
FileShare shareMode,
|
||||||
|
IntPtr securityAttributes,
|
||||||
|
FileMode creationDisposition,
|
||||||
|
uint flagsAndAttributes,
|
||||||
|
IntPtr templateFile);
|
||||||
|
|
||||||
|
internal enum GenericAccess : uint
|
||||||
|
{
|
||||||
|
Read = 0x80000000,
|
||||||
|
Write = 0x40000000,
|
||||||
|
Execute = 0x20000000,
|
||||||
|
All = 0x10000000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using static WinSW.Native.HandleApis;
|
||||||
|
|
||||||
|
namespace WinSW.Native
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal readonly ref struct Handle
|
||||||
|
{
|
||||||
|
private readonly IntPtr handle;
|
||||||
|
|
||||||
|
internal Handle(IntPtr handle) => this.handle = handle;
|
||||||
|
|
||||||
|
public void Dispose() => CloseHandle(this.handle);
|
||||||
|
|
||||||
|
public static implicit operator IntPtr(Handle value) => value.handle;
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ namespace WinSW.Native
|
||||||
internal static extern bool OpenProcessToken(
|
internal static extern bool OpenProcessToken(
|
||||||
IntPtr processHandle,
|
IntPtr processHandle,
|
||||||
TokenAccessLevels desiredAccess,
|
TokenAccessLevels desiredAccess,
|
||||||
out IntPtr tokenHandle);
|
out Handle tokenHandle);
|
||||||
|
|
||||||
internal enum PROCESSINFOCLASS
|
internal enum PROCESSINFOCLASS
|
||||||
{
|
{
|
||||||
|
|
|
@ -90,5 +90,15 @@ namespace WinSW.Native
|
||||||
_ = LsaClose(policyHandle);
|
_ = LsaClose(policyHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool IsSpecialAccount(string accountName) => accountName switch
|
||||||
|
{
|
||||||
|
@"LocalSystem" => true,
|
||||||
|
@".\LocalSystem" => true,
|
||||||
|
@"NT AUTHORITY\LocalService" => true,
|
||||||
|
@"NT AUTHORITY\NetworkService" => true,
|
||||||
|
string name when name == $@"{Environment.MachineName}\LocalSystem" => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace WinSW.Tasks
|
||||||
|
|
||||||
public override bool Execute()
|
public override bool Execute()
|
||||||
{
|
{
|
||||||
using var module = ModuleDefinition.ReadModule(this.Path, new ReaderParameters { ReadWrite = true });
|
using var module = ModuleDefinition.ReadModule(this.Path, new() { ReadWrite = true, ReadSymbols = true });
|
||||||
|
|
||||||
this.WalkType(module.EntryPoint.DeclaringType);
|
this.WalkType(module.EntryPoint.DeclaringType);
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ namespace WinSW.Tasks
|
||||||
types.RemoveAt(i);
|
types.RemoveAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.Write();
|
module.Write(new WriterParameters { WriteSymbols = true });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ using System.Reflection;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.ServiceProcess;
|
using System.ServiceProcess;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using log4net;
|
using log4net;
|
||||||
using log4net.Appender;
|
using log4net.Appender;
|
||||||
|
@ -440,12 +439,12 @@ namespace WinSW
|
||||||
username = config.ServiceAccountUserName ?? username;
|
username = config.ServiceAccountUserName ?? username;
|
||||||
password = config.ServiceAccountPassword ?? password;
|
password = config.ServiceAccountPassword ?? password;
|
||||||
|
|
||||||
if (username is null || password is null && !IsSpecialAccount(username))
|
if (username is null || password is null && !Security.IsSpecialAccount(username))
|
||||||
{
|
{
|
||||||
switch (config.ServiceAccountPrompt)
|
switch (config.ServiceAccountPrompt)
|
||||||
{
|
{
|
||||||
case "dialog":
|
case "dialog":
|
||||||
Credentials.PropmtForCredentialsDialog(
|
Credentials.PromptForCredentialsDialog(
|
||||||
ref username,
|
ref username,
|
||||||
ref password,
|
ref password,
|
||||||
"Windows Service Wrapper",
|
"Windows Service Wrapper",
|
||||||
|
@ -453,13 +452,13 @@ namespace WinSW
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "console":
|
case "console":
|
||||||
PromptForCredentialsConsole();
|
Credentials.PromptForCredentialsConsole(ref username, ref password);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username != null && !IsSpecialAccount(username))
|
if (username != null && !Security.IsSpecialAccount(username))
|
||||||
{
|
{
|
||||||
Security.AddServiceLogonRight(ref username);
|
Security.AddServiceLogonRight(ref username);
|
||||||
}
|
}
|
||||||
|
@ -509,34 +508,7 @@ namespace WinSW
|
||||||
EventLog.CreateEventSource(eventLogSource, "Application");
|
EventLog.CreateEventSource(eventLogSource, "Application");
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info($"Service '{config.Format()}' was installed successfully.");
|
Log.Info($"Service '{config.Format()}' was installed successfully.");
|
||||||
|
|
||||||
void PromptForCredentialsConsole()
|
|
||||||
{
|
|
||||||
if (username is null)
|
|
||||||
{
|
|
||||||
Console.Write("Username: ");
|
|
||||||
username = Console.ReadLine()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password is null && !IsSpecialAccount(username))
|
|
||||||
{
|
|
||||||
Console.Write("Password: ");
|
|
||||||
password = ReadPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsSpecialAccount(string accountName) => accountName switch
|
|
||||||
{
|
|
||||||
@"LocalSystem" => true,
|
|
||||||
@".\LocalSystem" => true,
|
|
||||||
@"NT AUTHORITY\LocalService" => true,
|
|
||||||
@"NT AUTHORITY\NetworkService" => true,
|
|
||||||
string name when name == $@"{Environment.MachineName}\LocalSystem" => true,
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Uninstall(string? pathToConfig, bool noElevate)
|
void Uninstall(string? pathToConfig, bool noElevate)
|
||||||
|
@ -1228,7 +1200,7 @@ namespace WinSW
|
||||||
Throw.Command.Win32Exception("Failed to open process token.");
|
Throw.Command.Win32Exception("Failed to open process token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
using (token)
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
@ -1245,33 +1217,6 @@ namespace WinSW
|
||||||
return elevation.TokenIsElevated != 0;
|
return elevation.TokenIsElevated != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_ = HandleApis.CloseHandle(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ReadPassword()
|
|
||||||
{
|
|
||||||
var buf = new StringBuilder();
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var key = Console.ReadKey(true);
|
|
||||||
if (key.Key == ConsoleKey.Enter)
|
|
||||||
{
|
|
||||||
return buf.ToString();
|
|
||||||
}
|
|
||||||
else if (key.Key == ConsoleKey.Backspace)
|
|
||||||
{
|
|
||||||
_ = buf.Remove(buf.Length - 1, 1);
|
|
||||||
Console.Write("\b \b");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.Write('*');
|
|
||||||
_ = buf.Append(key.KeyChar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,9 +39,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0-windows'">
|
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0-windows'">
|
||||||
<ProjectReference Include="..\WinSW.Tasks\WinSW.Tasks.csproj">
|
<ProjectReference Include="..\WinSW.Tasks\WinSW.Tasks.csproj" ReferenceOutputAssembly="false" />
|
||||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PublishCoreZip" AfterTargets="Publish" Condition="'$(TargetFramework)' == 'net5.0-windows' and '$(IncludeNativeLibrariesForSelfExtract)' != 'true'">
|
<Target Name="PublishCoreZip" AfterTargets="Publish" Condition="'$(TargetFramework)' == 'net5.0-windows' and '$(IncludeNativeLibrariesForSelfExtract)' != 'true'">
|
||||||
|
|
Loading…
Reference in New Issue