public static ProcessInTheJob CreateProcessInTheJob( string desktopName, string appPath, string cmdLine, IntPtr jobHandle) { var newJobHandle = IntPtr.Zero; if (jobHandle == IntPtr.Zero) { newJobHandle = jobHandle = WinApi.CreateJobObject(IntPtr.Zero, null); // TODO set limits etc } const uint NORMAL_PRIORITY_CLASS = 0x0020; const uint CREATE_SUSPENDED = 0x00000004; var sInfo = new WinApi.STARTUPINFO(); sInfo.cb = Marshal.SizeOf(sInfo); sInfo.lpDesktop = desktopName; var pSec = new WinApi.SECURITY_ATTRIBUTES(); var tSec = new WinApi.SECURITY_ATTRIBUTES(); pSec.nLength = Marshal.SizeOf(pSec); tSec.nLength = Marshal.SizeOf(tSec); string commandLine = ""; if (!string.IsNullOrEmpty(appPath) && !string.IsNullOrEmpty(cmdLine)) { commandLine = $"{appPath} {cmdLine}"; } else if (!string.IsNullOrEmpty(cmdLine)) { commandLine = cmdLine; } var retValue = WinApi.CreateProcess(appPath, commandLine, ref pSec, ref tSec, false, NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED, IntPtr.Zero, null, ref sInfo, out var pInfo); WriteLine("Process ID (PID): " + pInfo.dwProcessId); WriteLine("Process Handle : " + pInfo.hProcess); var r = WinApi.AssignProcessToJobObject(jobHandle, pInfo.hProcess); if (!r) { if (newJobHandle != IntPtr.Zero) { WinApi.CloseHandle(newJobHandle); } return(new ProcessInTheJob(ProcessInTheJob.Status.COULD_NOT_ASSIGN_JOB, IntPtr.Zero, pInfo)); } // Ensure that killing one process kills the others var extendedInfo = new WinApi.JOBOBJECT_EXTENDED_LIMIT_INFORMATION { BasicLimitInformation = { LimitFlags = (short)WinApi.LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE } }; int length = Marshal.SizeOf(typeof(WinApi.JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); if (!WinApi.SetInformationJobObject(jobHandle, WinApi.JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, extendedInfoPtr, (uint)length)) { throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error())); } Marshal.FreeHGlobal(extendedInfoPtr); WinApi.ResumeThread(pInfo.hThread); return(new ProcessInTheJob(ProcessInTheJob.Status.JOB_ASSIGNED, jobHandle, pInfo)); }
static int Main(string[] cmdArgs) { var argMap = Args.Parse(cmdArgs, new HashSet <string> { "app", "desktop" }); if (cmdArgs.Length == 0) { WriteLine("Usage: headless <appname> [--<option-name> <option-value...] -- <app args>"); WriteLine("Available options are:"); WriteLine(" --app <appname> - application to start. Alternative way to set an app."); WriteLine(" --desktop - desktop name. Leave unspecified for random name."); WriteLine(" --desktop=false - do not use virtual desktop"); return(1); } WriteLine($"Starting headless '{string.Join(" ", cmdArgs)}'"); var appName = (argMap.Named.TryGetValue("app", out var val) ? val : System.Environment.GetEnvironmentVariable("HEADLESS_APP")) ?? (argMap.Positional.Count > 0 ? argMap.Positional[0] : null); var appPath = FindApplication(appName); if (appPath == null) { WriteLine($"ERR: application '{appName}' not found'"); return(1); } WriteLine($"INFO: starting application {appPath}"); var rand = new System.Random(); // shaping desktop name string desktopName = null; bool useHiddenDesktop = false; if (argMap.Named.TryGetValue("desktop", out val) && !string.IsNullOrWhiteSpace(val)) { if (val != "false") { desktopName = val; useHiddenDesktop = true; } } else { desktopName = $"Headless-{rand.Next(int.MaxValue)}"; useHiddenDesktop = true; } var cleanupActions = new List <Action>(); CancelKeyPress += (s, ev) => { WriteLine("Ctrl+C pressed"); Cleanup(cleanupActions); ev.Cancel = true; }; // get desktop name from params var desktopHandle = IntPtr.Zero; if (useHiddenDesktop) { desktopHandle = DesktopUtils.CreateDesktop(desktopName); WriteLine($"INFO: opened desktop '{desktopName}'"); cleanupActions.Add(() => { WriteLine($"INFO: closing desktop {desktopHandle}"); DesktopUtils.CloseDesktop(desktopHandle); }); } var argsEscaped = from r in argMap.Reminder select $"\"{r}\""; var commandLine = String.Join(" ", argsEscaped); WriteLine($"INFO: Starting app with cmdline '{commandLine}'"); // TODO desktop default var jobProcess = DesktopUtils.CreateProcessInTheJob(desktopName, appPath, commandLine, IntPtr.Zero); if (jobProcess.status == DesktopUtils.ProcessInTheJob.Status.COULD_NOT_ASSIGN_JOB) { WriteLine("ERROR: Failed to assign job"); WinApi.ResumeThread(jobProcess.processInfo.hThread); } else { cleanupActions.Add(() => { WriteLine($"INFO: closing job {jobProcess.jobHandle}"); Console.Beep(); WinApi.CloseHandle(jobProcess.jobHandle); }); } cleanupActions.Add(() => { WriteLine($"INFO: closing process {jobProcess.processInfo.hProcess}"); WinApi.CloseHandle(jobProcess.processInfo.hProcess); }); if (useHiddenDesktop) { cleanupActions.Add(() => { WriteLine("INFO: closing desktop windows"); DesktopUtils.CloseDesktopWindows(desktopName); }); } WriteLine($"INFO: app process id '{System.Diagnostics.Process.GetCurrentProcess().Id}'"); WinApi.CloseHandle(jobProcess.processInfo.hThread); WinApi.WaitForSingleObject(jobProcess.processInfo.hProcess, WinApi.INFINITE); Cleanup(cleanupActions); return(0); }