From fa4471a9e64fda1fe94887b424a081df8d10de78 Mon Sep 17 00:00:00 2001 From: Ani <115020168+drawbyperpetual@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:58:29 +0100 Subject: [PATCH] [MWB] Fix file transfer not working in service mode (#37542) * [MWB] Fix file transfer not working in service mode * Spellcheck issues --- .../App/Class/Common.Helper.cs | 27 +++++++++++++- .../App/Class/Common.Launch.cs | 15 ++++++-- .../App/Class/NativeMethods.cs | 37 ++++++++++++++++++- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.Helper.cs b/src/modules/MouseWithoutBorders/App/Class/Common.Helper.cs index a27df9a073..d6bd73b7eb 100644 --- a/src/modules/MouseWithoutBorders/App/Class/Common.Helper.cs +++ b/src/modules/MouseWithoutBorders/App/Class/Common.Helper.cs @@ -9,6 +9,7 @@ using System.Drawing; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Principal; using System.Windows.Forms; @@ -443,7 +444,31 @@ namespace MouseWithoutBorders { _ = Common.ImpersonateLoggedOnUserAndDoSomething(() => { - Setting.Values.Username = WindowsIdentity.GetCurrent(true).Name; + // See: https://stackoverflow.com/questions/19487541/how-to-get-windows-user-name-from-sessionid + static string GetUsernameBySessionId(int sessionId) + { + string username = "SYSTEM"; + if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTSInfoClass.WTSUserName, out nint buffer, out int strLen) && strLen > 1) + { + username = Marshal.PtrToStringAnsi(buffer); + NativeMethods.WTSFreeMemory(buffer); + + if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTSInfoClass.WTSDomainName, out buffer, out strLen) && strLen > 1) + { + username = @$"{Marshal.PtrToStringAnsi(buffer)}\{username}"; + NativeMethods.WTSFreeMemory(buffer); + } + } + + return username; + } + + // The most direct way to fetch the username is WindowsIdentity.GetCurrent(true).Name + // but GetUserName can run within an ExecutionContext.SuppressFlow block, which creates issues + // with WindowsIdentity.GetCurrent. + // See: https://stackoverflow.com/questions/76998988/exception-when-using-executioncontext-suppressflow-in-net-7 + // So we use WTSQuerySessionInformation as a workaround. + Setting.Values.Username = GetUsernameBySessionId(Process.GetCurrentProcess().SessionId); }); } else diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.Launch.cs b/src/modules/MouseWithoutBorders/App/Class/Common.Launch.cs index 5704e6119d..76b811b8f8 100644 --- a/src/modules/MouseWithoutBorders/App/Class/Common.Launch.cs +++ b/src/modules/MouseWithoutBorders/App/Class/Common.Launch.cs @@ -5,7 +5,6 @@ using System; using System.ComponentModel; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.InteropServices; using System.Security.Principal; @@ -39,23 +38,31 @@ namespace MouseWithoutBorders } else { + // SuppressFlow fixes an issue on service mode, where WTSQueryUserToken runs successfully once and then fails + // on subsequent calls. The reason appears to be an unknown issue with reverting the impersonation, + // meaning that subsequent impersonation attempts run as the logged-on user and fail. + // This is a workaround. + using var asyncFlowControl = System.Threading.ExecutionContext.SuppressFlow(); + uint dwSessionId; IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero; try { dwSessionId = (uint)Process.GetCurrentProcess().SessionId; uint rv = NativeMethods.WTSQueryUserToken(dwSessionId, ref hUserToken); - Logger.LogDebug("WTSQueryUserToken returned " + rv.ToString(CultureInfo.CurrentCulture)); + var lastError = rv == 0 ? Marshal.GetLastWin32Error() : 0; + + Logger.LogDebug($"{nameof(NativeMethods.WTSQueryUserToken)} returned {rv.ToString(CultureInfo.CurrentCulture)}"); if (rv == 0) { - Logger.LogDebug($"WTSQueryUserToken failed with: {Marshal.GetLastWin32Error()}."); + Logger.Log($"{nameof(NativeMethods.WTSQueryUserToken)} failed with: {lastError}."); return false; } if (!NativeMethods.DuplicateToken(hUserToken, (int)NativeMethods.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, ref hUserTokenDup)) { - Logger.TelemetryLogTrace($"DuplicateToken Failed! {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning); + Logger.TelemetryLogTrace($"{nameof(NativeMethods.DuplicateToken)} Failed! {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning); _ = NativeMethods.CloseHandle(hUserToken); _ = NativeMethods.CloseHandle(hUserTokenDup); return false; diff --git a/src/modules/MouseWithoutBorders/App/Class/NativeMethods.cs b/src/modules/MouseWithoutBorders/App/Class/NativeMethods.cs index 01371bb2ac..da6ce18920 100644 --- a/src/modules/MouseWithoutBorders/App/Class/NativeMethods.cs +++ b/src/modules/MouseWithoutBorders/App/Class/NativeMethods.cs @@ -75,6 +75,41 @@ namespace MouseWithoutBorders.Class [DllImport("kernel32.dll")] internal static extern uint WTSGetActiveConsoleSessionId(); + [DllImport("Wtsapi32.dll")] + internal static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WTSInfoClass infoClass, out IntPtr ppBuffer, out int pBytesReturned); + + [DllImport("Wtsapi32.dll")] + internal static extern void WTSFreeMemory(IntPtr pointer); + + internal enum WTSInfoClass + { + WTSInitialProgram, + WTSApplicationName, + WTSWorkingDirectory, + WTSOEMId, + WTSSessionId, + WTSUserName, + WTSWinStationName, + WTSDomainName, + WTSConnectState, + WTSClientBuildNumber, + WTSClientName, + WTSClientDirectory, + WTSClientProductId, + WTSClientHardwareId, + WTSClientAddress, + WTSClientDisplay, + WTSClientProtocolType, + WTSIdleTime, + WTSLogonTime, + WTSIncomingBytes, + WTSOutgoingBytes, + WTSIncomingFrames, + WTSOutgoingFrames, + WTSClientInfo, + WTSSessionInfo, + } + #endif [DllImport("user32.dll", SetLastError = true)] @@ -812,7 +847,7 @@ namespace MouseWithoutBorders.Class // [DllImport("kernel32.dll", SetLastError = true)] // internal static extern IntPtr CreateToolhelp32Snapshot(UInt32 dwFlags, UInt32 th32ProcessID); - [DllImport("Wtsapi32.dll")] + [DllImport("Wtsapi32.dll", SetLastError = true)] internal static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "1", Justification = "Dotnet port with style preservation")]