/// <summary> /// Starts task process. /// Returns (processId, processHandle). Throws if failed. /// </summary> static (int pid, WaitHandle hProcess) _StartProcess(_SpUac uac, string exeFile, string args, string wrPipeName) { if (wrPipeName != null) { wrPipeName = "ATask.WriteResult.pipe=" + wrPipeName; } if (uac == _SpUac.admin) { if (wrPipeName != null) { throw new AuException($"*start process '{exeFile}' as admin and enable ATask.WriteResult"); //cannot pass environment variables. //rare //FUTURE } var k = AExec.Run(exeFile, args, RFlags.Admin | RFlags.NeedProcessHandle, ""); return(k.ProcessId, k.ProcessHandle); //note: don't try to start task without UAC consent. It is not secure. // Normally Au editor runs as admin in admin user account, and don't need to go through this. } else { var ps = new Au.Util.ProcessStarter_(exeFile, args, "", envVar: wrPipeName, rawExe: true); var need = Au.Util.ProcessStarter_.Result.Need.WaitHandle; var psr = uac == _SpUac.userFromAdmin ? ps.StartUserIL(need) : ps.Start(need, inheritUiaccess: uac == _SpUac.uiAccess); return(psr.pid, psr.waitHandle); } }
/// <summary> /// Executes the compiled assembly in new process. /// Returns: process id if started now, 0 if failed, (int)ATask.ERunResult.deferred if scheduled to run later. /// </summary> /// <param name="f"></param> /// <param name="r"></param> /// <param name="args"></param> /// <param name="noDefer">Don't schedule to run later. If cannot run now, just return 0.</param> /// <param name="wrPipeName">Pipe name for ATask.WriteResult.</param> /// <param name="ignoreLimits">Don't check whether the task can run now.</param> /// <param name="runFromEditor">Starting from the Run button or menu Run command.</param> public unsafe int RunCompiled(FileNode f, Compiler.CompResults r, string[] args, bool noDefer = false, string wrPipeName = null, bool ignoreLimits = false, bool runFromEditor = false) { g1: if (!ignoreLimits && !_CanRunNow(f, r, out var running, runFromEditor)) { var ifRunning = r.ifRunning; bool same = running.f == f; if (!same) { ifRunning = r.ifRunning2 switch { EIfRunning2.cancel => EIfRunning.cancel, EIfRunning2.wait => EIfRunning.wait, EIfRunning2.warn => EIfRunning.warn, _ => (ifRunning & ~EIfRunning._restartFlag) switch { EIfRunning.cancel => EIfRunning.cancel, EIfRunning.wait => EIfRunning.wait, _ => EIfRunning.warn } }; } else if (ifRunning.Has(EIfRunning._restartFlag)) { if (runFromEditor) { ifRunning = EIfRunning.restart; } else { ifRunning &= ~EIfRunning._restartFlag; } } //AOutput.Write(same, ifRunning); switch (ifRunning) { case EIfRunning.cancel: break; case EIfRunning.wait when !noDefer: _q.Insert(0, new _WaitingTask(f, r, args)); return((int)ATask.ERunResult.deferred); //-1 case EIfRunning.restart when _EndTask(running): goto g1; default: //warn string s1 = same ? "it" : $"{running.f.SciLink}"; AOutput.Write($"<>Cannot start {f.SciLink} because {s1} is running. You may want to <+properties \"{f.IdStringWithWorkspace}\">change<> <c green>ifRunning<>, <c green>ifRunning2<>, <c green>runMode<>."); break; } return(0); } _SpUac uac = _SpUac.normal; int preIndex = 0; if (!AUac.IsUacDisabled) { //info: to completely disable UAC on Win7: gpedit.msc/Computer configuration/Windows settings/Security settings/Local policies/Security options/User Account Control:Run all administrators in Admin Approval Mode/Disabled. Reboot. //note: when UAC disabled, if our uac is System, IsUacDisabled returns false (we probably run as SYSTEM user). It's OK. var IL = AUac.OfThisProcess.IntegrityLevel; if (r.uac == EUac.inherit) { switch (IL) { case UacIL.High: preIndex = 1; break; case UacIL.UIAccess: uac = _SpUac.uiAccess; preIndex = 2; break; } } else { switch (IL) { case UacIL.Medium: case UacIL.UIAccess: if (r.uac == EUac.admin) { uac = _SpUac.admin; } break; case UacIL.High: if (r.uac == EUac.user) { uac = _SpUac.userFromAdmin; } break; case UacIL.Low: case UacIL.Untrusted: case UacIL.Unknown: //break; case UacIL.System: case UacIL.Protected: AOutput.Write($"<>Cannot run {f.SciLink}. Meta comment option <c green>uac {r.uac}<> cannot be used when the UAC integrity level of this process is {IL}. Supported levels are Medium, High and uiAccess."); return(0); //info: cannot start Medium IL process from System process. Would need another function. Never mind. } if (r.uac == EUac.admin) { preIndex = 1; } } } string exeFile, argsString; _Preloaded pre = null; byte[] taskParams = null; bool bit32 = r.prefer32bit || AVersion.Is32BitOS; if (r.notInCache) //meta role exeProgram { exeFile = Compiler.DllNameToAppHostExeName(r.file, bit32); argsString = args == null ? null : Au.Util.AStringUtil.CommandLineFromArray(args); } else { exeFile = AFolders.ThisAppBS + (bit32 ? "Au.Task32.exe" : "Au.Task.exe"); //int iFlags = r.hasConfig ? 1 : 0; int iFlags = 0; if (r.mtaThread) { iFlags |= 2; } if (r.console) { iFlags |= 4; } taskParams = Au.Util.Serializer_.SerializeWithSize(r.name, r.file, r.pdbOffset, iFlags, args, r.fullPathRefs, wrPipeName, (string)AFolders.Workspace); wrPipeName = null; if (bit32 && !AVersion.Is32BitOS) { preIndex += 3; } pre = s_preloaded[preIndex] ??= new _Preloaded(preIndex); argsString = pre.pipeName; } int pid; WaitHandle hProcess = null; bool disconnectPipe = false; try { //APerf.First(); var pp = pre?.hProcess; if (pp != null && 0 != Api.WaitForSingleObject(pp.SafeWaitHandle.DangerousGetHandle(), 0)) //preloaded process exists { hProcess = pp; pid = pre.pid; pre.hProcess = null; pre.pid = 0; } else { if (pp != null) { pp.Dispose(); pre.hProcess = null; pre.pid = 0; } //preloaded process existed but somehow ended (pid, hProcess) = _StartProcess(uac, exeFile, argsString, wrPipeName); } Api.AllowSetForegroundWindow(pid); if (pre != null) { //APerf.First(); var o = new Api.OVERLAPPED { hEvent = pre.overlappedEvent }; if (!Api.ConnectNamedPipe(pre.hPipe, &o)) { int e = ALastError.Code; if (e != Api.ERROR_PIPE_CONNECTED) { if (e != Api.ERROR_IO_PENDING) { throw new AuException(e); } var ha = stackalloc IntPtr[2] { pre.overlappedEvent, hProcess.SafeWaitHandle.DangerousGetHandle() }; int wr = Api.WaitForMultipleObjectsEx(2, ha, false, -1, false); if (wr != 0) { Api.CancelIo(pre.hPipe); throw new AuException("*start task. Preloaded task process ended"); } //note: if fails when 32-bit process, rebuild solution with platform x86 disconnectPipe = true; if (!Api.GetOverlappedResult(pre.hPipe, ref o, out _, false)) { throw new AuException(0); } } } //APerf.Next(); if (!Api.WriteFileArr(pre.hPipe, taskParams, out _)) { throw new AuException(0); } //APerf.Next(); Api.DisconnectNamedPipe(pre.hPipe); disconnectPipe = false; //APerf.NW('e'); //start preloaded process for next task. Let it wait for pipe connection. if (uac != _SpUac.admin) //we don't want second UAC consent { try { (pre.pid, pre.hProcess) = _StartProcess(uac, exeFile, argsString, null); } catch (Exception ex) { ADebug.Print(ex); } } } } catch (Exception ex) { AOutput.Write(ex); if (disconnectPipe) { Api.DisconnectNamedPipe(pre.hPipe); } hProcess?.Dispose(); return(0); } var rt = new RunningTask(f, hProcess, r.runMode != ERunMode.green); _Add(rt); return(pid); }