using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; using System.Text; using JetBrains.Annotations; using Microsoft.Win32.SafeHandles; namespace VirtualPrinter.Utils { [SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] internal static class Win32Sys { /// /// Creates process with given command line via Win32 API. /// Use this method to start a process as a service. /// /// /// /// internal static void CreateProcessAsUser(int sessId, string user, string commandLine) { WTSQueryUserToken((uint) sessId, out var userToken); if (userToken.IsInvalid) { throw new InvalidOperationException($"Could not query user token for session {sessId}! (" + GetLastError() + ")", Windows.LastError); } CreateProcessAsUser(userToken, user, commandLine); } private static void CreateProcessAsUser([NotNull]SafeTokenHandle token, string user, string commandLine) { var processInformation = new PROCESS_INFORMATION(); try { var securityAttributes = new SECURITY_ATTRIBUTES(); securityAttributes.Length = Marshal.SizeOf(securityAttributes); var startupInfo = new STARTUPINFO(); startupInfo.cb = Marshal.SizeOf(startupInfo); startupInfo.lpDesktop = "winsta0\\default"; var info = new ProfileInfo(); info.dwSize = Marshal.SizeOf(info); info.lpUserName = user; info.dwFlags = 1; var result = LoadUserProfile(token, ref info); if (!result) { throw Windows.LastError; } result = CreateEnvironmentBlock(out var lpEnvironment, token, false); if (!result) { throw Windows.LastError; } result = CreateProcessAsUser ( token, null, commandLine, ref securityAttributes, ref securityAttributes, false, 0x00000400, lpEnvironment, null, ref startupInfo, ref processInformation ); if (!result) { throw Windows.LastError; } } finally { if (processInformation.hProcess != IntPtr.Zero) { CloseMyHandle(processInformation.hProcess); } if (processInformation.hThread != IntPtr.Zero) { CloseMyHandle(processInformation.hThread); } if (!token.IsInvalid) { token.Dispose(); } } } #region Classes public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeTokenHandle() : base(true) { } [DllImport("kernel32.dll")] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr handle); protected override bool ReleaseHandle() { return CloseHandle(handle); } public override string ToString() { return $"{handle}"; } } #endregion #region Structures [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessID; public int dwThreadID; } [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public int cb; public string lpReserved; public string lpDesktop; public string lpTitle; public int dwX; public int dwY; public int dwXSize; public int dwXCountChars; public int dwYCountChars; public int dwFillAttribute; public int dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct ProfileInfo { public int dwSize; public int dwFlags; public string lpUserName; public string lpProfilePath; public string lpDefaultPath; public string lpServerName; public string lpPolicyPath; public IntPtr hProfile; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int Length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } #endregion #region Windows interop [DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool LoadUserProfile(SafeTokenHandle hToken, ref ProfileInfo lpProfileInfo); [DllImport("userenv.dll", SetLastError = true)] private static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, SafeTokenHandle hToken, bool bInherit); [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern bool CloseMyHandle(IntPtr handle); [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] private static extern bool CreateProcessAsUser(SafeTokenHandle hToken, string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation); [DllImport("wtsapi32.dll", SetLastError = true)] private static extern bool WTSQueryUserToken(uint sessionId, out SafeTokenHandle Token); [DllImport("kernel32.dll")] public static extern uint GetLastError(); [DllImport("kernel32")] public static extern int GetPrivateProfileString ( string section, string key, string def, StringBuilder retVal, int size, string filePath ); [DllImport("kernel32")] public static extern long WritePrivateProfileString ( string section, string key, string val, string filePath ); #endregion } }