diff --git a/src/WinSW.Core/Native/ConsoleApis.cs b/src/WinSW.Core/Native/ConsoleApis.cs
index b3b0bb7..c687e0f 100644
--- a/src/WinSW.Core/Native/ConsoleApis.cs
+++ b/src/WinSW.Core/Native/ConsoleApis.cs
@@ -1,6 +1,8 @@
#pragma warning disable SA1310 // Field names should not contain underscore
+using System;
using System.Runtime.InteropServices;
+using static WinSW.Native.Libraries;
namespace WinSW.Native
{
@@ -10,24 +12,41 @@ namespace WinSW.Native
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();
- [DllImport(Libraries.Kernel32, SetLastError = true)]
+ [DllImport(Kernel32, SetLastError = true)]
internal static extern bool AttachConsole(int processId);
- [DllImport(Libraries.Kernel32)]
+ [DllImport(Kernel32)]
internal static extern bool FreeConsole();
- [DllImport(Libraries.Kernel32)]
+ [DllImport(Kernel32)]
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);
- [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);
+ [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 enum CtrlEvents : uint
diff --git a/src/WinSW.Core/Native/ConsoleEx.cs b/src/WinSW.Core/Native/ConsoleEx.cs
new file mode 100644
index 0000000..b67f3f3
--- /dev/null
+++ b/src/WinSW.Core/Native/ConsoleEx.cs
@@ -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);
+ }
+
+ ///
+ 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.");
+ }
+ }
+ }
+}
diff --git a/src/WinSW.Core/Native/Credentials.cs b/src/WinSW.Core/Native/Credentials.cs
index 2537634..fd2fe94 100644
--- a/src/WinSW.Core/Native/Credentials.cs
+++ b/src/WinSW.Core/Native/Credentials.cs
@@ -7,7 +7,24 @@ namespace WinSW.Native
{
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;
password ??= string.Empty;
diff --git a/src/WinSW.Core/Native/FileApis.cs b/src/WinSW.Core/Native/FileApis.cs
new file mode 100644
index 0000000..4a97d2d
--- /dev/null
+++ b/src/WinSW.Core/Native/FileApis.cs
@@ -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,
+ }
+ }
+}
diff --git a/src/WinSW.Core/Native/Handle.cs b/src/WinSW.Core/Native/Handle.cs
new file mode 100644
index 0000000..1a8ce60
--- /dev/null
+++ b/src/WinSW.Core/Native/Handle.cs
@@ -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;
+ }
+}
diff --git a/src/WinSW.Core/Native/ProcessApis.cs b/src/WinSW.Core/Native/ProcessApis.cs
index 15b5af1..e54fba5 100644
--- a/src/WinSW.Core/Native/ProcessApis.cs
+++ b/src/WinSW.Core/Native/ProcessApis.cs
@@ -38,7 +38,7 @@ namespace WinSW.Native
internal static extern bool OpenProcessToken(
IntPtr processHandle,
TokenAccessLevels desiredAccess,
- out IntPtr tokenHandle);
+ out Handle tokenHandle);
internal enum PROCESSINFOCLASS
{
diff --git a/src/WinSW.Core/Native/Security.cs b/src/WinSW.Core/Native/Security.cs
index 503ae45..f7ca5c8 100644
--- a/src/WinSW.Core/Native/Security.cs
+++ b/src/WinSW.Core/Native/Security.cs
@@ -90,5 +90,15 @@ namespace WinSW.Native
_ = 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
+ };
}
}
diff --git a/src/WinSW.Tasks/Trim.cs b/src/WinSW.Tasks/Trim.cs
index b8a1b90..1895189 100644
--- a/src/WinSW.Tasks/Trim.cs
+++ b/src/WinSW.Tasks/Trim.cs
@@ -15,7 +15,7 @@ namespace WinSW.Tasks
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);
@@ -46,7 +46,7 @@ namespace WinSW.Tasks
types.RemoveAt(i);
}
- module.Write();
+ module.Write(new WriterParameters { WriteSymbols = true });
return true;
}
diff --git a/src/WinSW/Program.cs b/src/WinSW/Program.cs
index 5cc7b06..5086f26 100644
--- a/src/WinSW/Program.cs
+++ b/src/WinSW/Program.cs
@@ -13,7 +13,6 @@ using System.Reflection;
using System.Security.AccessControl;
using System.Security.Principal;
using System.ServiceProcess;
-using System.Text;
using System.Threading;
using log4net;
using log4net.Appender;
@@ -440,12 +439,12 @@ namespace WinSW
username = config.ServiceAccountUserName ?? username;
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)
{
case "dialog":
- Credentials.PropmtForCredentialsDialog(
+ Credentials.PromptForCredentialsDialog(
ref username,
ref password,
"Windows Service Wrapper",
@@ -453,13 +452,13 @@ namespace WinSW
break;
case "console":
- PromptForCredentialsConsole();
+ Credentials.PromptForCredentialsConsole(ref username, ref password);
break;
}
}
}
- if (username != null && !IsSpecialAccount(username))
+ if (username != null && !Security.IsSpecialAccount(username))
{
Security.AddServiceLogonRight(ref username);
}
@@ -509,34 +508,7 @@ namespace WinSW
EventLog.CreateEventSource(eventLogSource, "Application");
}
- 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
- };
+ Log.Info($"Service '{config.Format()}' was installed successfully.");
}
void Uninstall(string? pathToConfig, bool noElevate)
@@ -1228,7 +1200,7 @@ namespace WinSW
Throw.Command.Win32Exception("Failed to open process token.");
}
- try
+ using (token)
{
unsafe
{
@@ -1245,33 +1217,6 @@ namespace WinSW
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);
- }
- }
}
}
}
diff --git a/src/WinSW/WinSW.csproj b/src/WinSW/WinSW.csproj
index 422b070..c895380 100644
--- a/src/WinSW/WinSW.csproj
+++ b/src/WinSW/WinSW.csproj
@@ -39,9 +39,7 @@
-
- false
-
+