using System.Diagnostics; using System.Runtime.InteropServices; namespace ITNexusAgent.Services; // Startet Prozesse in der aktiven User-Session (aus SYSTEM-Service heraus) // Korrekte Win32-Methode: WTSQueryUserToken + CreateProcessAsUser public static class SessionSpawner { #region Win32 P/Invoke [DllImport("kernel32.dll")] static extern uint WTSGetActiveConsoleSessionId(); [DllImport("Wtsapi32.dll", SetLastError = true)] static extern bool WTSQueryUserToken(uint sessionId, out IntPtr phToken); [DllImport("Wtsapi32.dll", SetLastError = true)] static extern bool WTSEnumerateSessions(IntPtr hServer, uint reserved, uint version, out IntPtr ppSessionInfo, out uint pCount); [DllImport("Wtsapi32.dll")] static extern void WTSFreeMemory(IntPtr pMemory); [DllImport("advapi32.dll", SetLastError = true)] static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, int impersonationLevel, int tokenType, out IntPtr phNewToken); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern bool CreateProcessAsUser(IntPtr hToken, string? lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string? lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("userenv.dll", SetLastError = true)] static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit); [DllImport("userenv.dll", SetLastError = true)] static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); [DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr hObject); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct STARTUPINFO { public int cb; public string? lpReserved; public string? lpDesktop; public string? lpTitle; public uint dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags; public ushort wShowWindow, cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput, hStdOutput, hStdError; } [StructLayout(LayoutKind.Sequential)] struct PROCESS_INFORMATION { public IntPtr hProcess, hThread; public uint dwProcessId, dwThreadId; } [StructLayout(LayoutKind.Sequential)] struct WTS_SESSION_INFO { public uint SessionId; [MarshalAs(UnmanagedType.LPStr)] public string? pWinStationName; public int State; // 0=Active } #endregion // Spawnt Prozess als eingeloggter Desktop-User. Gibt PID zurück oder -1 bei Fehler. public static int SpawnInUserSession(string exePath, string args) { var sessionId = FindActiveUserSession(); if (sessionId == uint.MaxValue) { AgentWorker.Log("SessionSpawner: Keine aktive User-Session gefunden"); return -1; } if (!WTSQueryUserToken(sessionId, out var userToken)) { AgentWorker.Log($"SessionSpawner: WTSQueryUserToken fehlgeschlagen (Session={sessionId}, Error={Marshal.GetLastWin32Error()})"); return -1; } try { if (!DuplicateTokenEx(userToken, 0x10000000u, IntPtr.Zero, 2, 1, out var dupToken)) { AgentWorker.Log($"SessionSpawner: DuplicateTokenEx fehlgeschlagen (Error={Marshal.GetLastWin32Error()})"); return -1; } try { CreateEnvironmentBlock(out var envBlock, dupToken, false); var si = new STARTUPINFO { cb = Marshal.SizeOf(), lpDesktop = "winsta0\\default", dwFlags = 1, // STARTF_USESHOWWINDOW wShowWindow = 1 // SW_SHOWNORMAL }; var cmdLine = $"\"{exePath}\" {args}"; bool ok = CreateProcessAsUser(dupToken, null, cmdLine, IntPtr.Zero, IntPtr.Zero, false, 0x0400, // CREATE_UNICODE_ENVIRONMENT envBlock, null, ref si, out var pi); if (envBlock != IntPtr.Zero) DestroyEnvironmentBlock(envBlock); if (!ok) { AgentWorker.Log($"SessionSpawner: CreateProcessAsUser fehlgeschlagen (Error={Marshal.GetLastWin32Error()})"); return -1; } CloseHandle(pi.hThread); CloseHandle(pi.hProcess); AgentWorker.Log($"SessionSpawner: Prozess gestartet (PID={pi.dwProcessId}, Session={sessionId})"); return (int)pi.dwProcessId; } finally { CloseHandle(dupToken); } } finally { CloseHandle(userToken); } } private static uint FindActiveUserSession() { // Zuerst Console-Session versuchen var consoleSession = WTSGetActiveConsoleSessionId(); if (consoleSession != uint.MaxValue && TryGetTokenForSession(consoleSession)) return consoleSession; // Alle Sessions durchsuchen → erste aktive (State=0) nehmen if (!WTSEnumerateSessions(IntPtr.Zero, 0, 1, out var pInfo, out var count)) return uint.MaxValue; try { var size = Marshal.SizeOf(); for (int i = 0; i < count; i++) { var info = Marshal.PtrToStructure(IntPtr.Add(pInfo, i * size)); if (info.State == 0 && info.SessionId != 0) // Active, nicht Session 0 return info.SessionId; } } finally { WTSFreeMemory(pInfo); } return uint.MaxValue; } private static bool TryGetTokenForSession(uint sessionId) { if (!WTSQueryUserToken(sessionId, out var tok)) return false; CloseHandle(tok); return true; } }