/// <summary> /// Starts blocking. /// </summary> /// <exception cref="ArgumentException"><i>what</i> is 0.</exception> /// <exception cref="InvalidOperationException">Already started.</exception> public void Start(BIEvents what) { if (_disposed) { throw new ObjectDisposedException(nameof(AInputBlocker)); } if (_block != 0) { throw new InvalidOperationException(); } if (!what.HasAny(BIEvents.All)) { throw new ArgumentException(); } _block = what; _startTime = ATime.WinMilliseconds; _syncEvent = Api.CreateEvent(false); _stopEvent = Api.CreateEvent(false); _threadHandle = Api.OpenThread(Api.SYNCHRONIZE, false, _threadId = Api.GetCurrentThreadId()); ThreadPool.QueueUserWorkItem(_this => (_this as AInputBlocker)._ThreadProc(), this); //SHOULDDO: what if thread pool is very busy? Eg if scripts use it incorrectly. Maybe better have own internal pool. Api.WaitForSingleObject(_syncEvent, Timeout.Infinite); GC.KeepAlive(this); }
/// <summary> /// Gets process executable file name (like "notepad.exe") or full path. /// Returns null if fails. /// </summary> /// <param name="processId">Process id.</param> /// <param name="fullPath"> /// Get full path. /// Note: Fails to get full path if the process belongs to another user session, unless current process is running as administrator; also fails to get full path of some system processes. /// </param> /// <param name="noSlowAPI">When the fast API QueryFullProcessImageName fails, don't try to use another much slower API WTSEnumerateProcesses. Not used if <i>fullPath</i> is true.</param> /// <remarks> /// This function is much slower than getting window name or class name. /// </remarks> /// <seealso cref="wnd.ProgramName"/> /// <seealso cref="wnd.ProgramPath"/> /// <seealso cref="wnd.ProcessId"/> public static string getName(int processId, bool fullPath = false, bool noSlowAPI = false) { if (processId == 0) { return(null); } string R = null; //var t = perf.mcs; //if(s_time != 0) print.it(t - s_time); //s_time = t; using var ph = Handle_.OpenProcess(processId); if (!ph.Is0) { //In non-admin process fails if the process is of another user session. //Also fails for some system processes: nvvsvc, nvxdsync, dwm. For dwm fails even in admin process. //getting native path is faster, but it gets like "\Device\HarddiskVolume5\Windows\System32\notepad.exe" and I don't know API to convert to normal if (_QueryFullProcessImageName(ph, !fullPath, out var s)) { R = s; if (pathname.IsPossiblyDos_(R)) { if (fullPath || _QueryFullProcessImageName(ph, false, out s)) { R = pathname.ExpandDosPath_(s); if (!fullPath) { R = _GetFileName(R); } } } } } else if (!noSlowAPI && !fullPath) { //the slow way. Can get only names, not paths. using (var p = new AllProcesses_(false)) { for (int i = 0; i < p.Count; i++) { if (p.Id(i) == processId) { R = p.Name(i, cannotOpen: true); break; } } } //TEST: NtQueryInformationProcess, like in getCommandLine. } return(R); //Would be good to cache process names here. But process id can be reused quickly. Use GetNameCached_ instead. // tested: a process id is reused after creating ~100 processes (and waiting until exits). It takes ~2 s. // The window finder is optimized to call this once for each process and not for each window. }
public _Preloaded(int index) { pipeName = $@"\\.\pipe\Au.Task-{Api.GetCurrentProcessId()}-{index}"; hPipe = Api.CreateNamedPipe(pipeName, Api.PIPE_ACCESS_OUTBOUND | Api.FILE_FLAG_OVERLAPPED, //use async pipe because editor would hang if task process exited without connecting. Same speed. Api.PIPE_TYPE_MESSAGE | Api.PIPE_REJECT_REMOTE_CLIENTS, 1, 0, 0, 0, null); overlappedEvent = Api.CreateEvent(false); }
public bool Init() { var tid = Api.GetCurrentThreadId(); pipeName = @"\\.\pipe\Au.CL-" + tid.ToString(); //will send this string to the task _hPipe = Api.CreateNamedPipe(pipeName, Api.PIPE_ACCESS_INBOUND | Api.FILE_FLAG_OVERLAPPED, //use async pipe because also need to wait for task process exit Api.PIPE_TYPE_MESSAGE | Api.PIPE_READMODE_MESSAGE | Api.PIPE_REJECT_REMOTE_CLIENTS, 1, 0, 0, 0, Api.SECURITY_ATTRIBUTES.ForPipes); return(!_hPipe.Is0); }
void _Alloc(int pid, AWnd w, int nBytes) { string err; const uint fl = Api.PROCESS_VM_OPERATION | Api.PROCESS_VM_READ | Api.PROCESS_VM_WRITE; _hproc = w.Is0 ? Handle_.OpenProcess(pid, fl) : Handle_.OpenProcess(w, fl); if (_hproc.Is0) { err = "Failed to open process handle."; goto ge; } if (nBytes != 0) { Mem = Api.VirtualAllocEx(_HprocHR, default, nBytes);
/// <summary> /// Gets DPI awareness of a window. /// </summary> /// <returns>Returns <b>Awareness.Invalid</b> if failed.</returns> /// <param name="w">A top-level window or control. Can belong to any process.</param> /// <remarks> /// Works best on Windows 10 1607 and later; uses API <msdn>GetWindowDpiAwarenessContext</msdn>. /// On Windows 8.1 returns <b>Awareness.PerMonitor</b> if <i>w</i> is of this process; else uses API <msdn>GetProcessDpiAwareness</msdn>, which is slower and less reliable. /// On Windows 7 and 8.0 always returns <b>System</b>, because there are no Windows API. /// </remarks> public static Awareness WindowDpiAwareness(wnd w) { if (osVersion.minWin10_1607) { return(Api.GetAwarenessFromDpiAwarenessContext(Api.GetWindowDpiAwarenessContext(w))); } else if (osVersion.minWin8_1) { if (w.IsOfThisProcess) { return(Awareness.PerMonitor); } using var hp = Handle_.OpenProcess(w); return((!hp.Is0 && 0 == Api.GetProcessDpiAwareness(hp, out var a)) ? a : Awareness.Invalid); } else { return(Awareness.System); //could use, IsWindowVirtualized (except if of this process), but slow and unreliable. } }
/// <summary> /// Runs a message loop. /// </summary> public unsafe void Loop() { bool isForms = 0 != (1 & Assembly_.IsLoadedFormsWpf()); using (isForms ? new EnsureWindowsFormsSynchronizationContext_(true) : null) { _loopEndEvent = Api.CreateEvent(true); try { _DoEvents(); for (; ;) { IntPtr ev = _loopEndEvent; int k = Api.MsgWaitForMultipleObjectsEx(1, &ev, 1000, Api.QS_ALLINPUT, Api.MWMO_INPUTAVAILABLE); if (k == Api.WAIT_TIMEOUT) { continue; //previously timeout was used to support Thread.Abort. It is disabled in Core, but maybe still safer with a timeout. } _DoEvents(); if (k == Api.WAIT_OBJECT_0 || k == Api.WAIT_FAILED) { break; //note: this is after DoEvents because may be posted messages when stopping loop. Although it seems that MsgWaitForMultipleObjects returns events after all messages. } if (Api.PeekMessage(out var _, default, Api.WM_QUIT, Api.WM_QUIT, Api.PM_NOREMOVE))
/// <summary> /// Runs/opens a program, document, directory (folder), URL, new email, Control Panel item etc. /// The returned <see cref="RResult"/> variable contains some process info - process id etc. /// </summary> /// <param name="file"> /// Examples: /// - <c>@"C:\file.txt"</c> /// - <c>folders.Documents</c> /// - <c>folders.System + "notepad.exe"</c> /// - <c>@"%folders.System%\notepad.exe"</c> /// - <c>@"%TMP%\file.txt"</c> /// - <c>"notepad.exe"</c> /// - <c>@"..\folder\x.exe"</c> /// - <c>"http://a.b.c/d"</c> /// - <c>"file:///path"</c> /// - <c>"mailto:[email protected]"</c> /// - <c>":: ITEMIDLIST"</c> /// - <c>@"shell:::{CLSID}"</c> /// - <c>@"shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"</c>. /// More info in Remarks. /// </param> /// <param name="args"> /// Command line arguments. /// This function expands environment variables if starts with <c>"%"</c> or <c>"\"%"</c>. /// </param> /// <param name="flags"></param> /// <param name="dirEtc"> /// Allows to specify more parameters: current directory, verb, etc. /// If string, it sets initial current directory for the new process. If "", gets it from <i>file</i>. More info: <see cref="ROptions.CurrentDirectory"/>. /// </param> /// <exception cref="ArgumentException">Used both <b>ROptions.Verb</b> and <b>RFlags.Admin</b> and this process isn't admin.</exception> /// <exception cref="AuException">Failed. For example, the file does not exist.</exception> /// <remarks> /// It works like when you double-click a file icon. It may start new process or not. For example it may just activate window if the program is already running. /// Uses API <msdn>ShellExecuteEx</msdn>. /// Similar to <see cref="Process.Start(string, string)"/>. /// /// The <i>file</i> parameter can be: /// - Full path of a file or directory. Examples: <c>@"C:\file.txt"</c>, <c>folders.Documents</c>, <c>folders.System + "notepad.exe"</c>, <c>@"%folders.System%\notepad.exe"</c>. /// - Filename of a file or directory, like <c>"notepad.exe"</c>. The function calls <see cref="filesystem.searchPath"/>. /// - Path relative to <see cref="folders.ThisApp"/>. Examples: <c>"x.exe"</c>, <c>@"subfolder\x.exe"</c>, <c>@".\subfolder\x.exe"</c>, <c>@"..\another folder\x.exe"</c>. /// - URL. Examples: <c>"https://www.example.com"</c>, <c>"file:///path"</c>. /// - Email, like <c>"mailto:[email protected]"</c>. Subject, body etc also can be specified, and Google knows how. /// - Shell object's ITEMIDLIST like <c>":: ITEMIDLIST"</c>. See <see cref="Pidl.ToHexString"/>, <see cref="folders.shell"/>. Can be used to open virtual folders and items like Control Panel. /// - Shell object's parsing name, like <c>@"shell:::{CLSID}"</c> or <c>@"::{CLSID}"</c>. See <see cref="Pidl.ToShellString"/>. Can be used to open virtual folders and items like Control Panel. /// - To run a Windows Store App, use <c>@"shell:AppsFolder\WinStoreAppId"</c> format. Examples: <c>@"shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"</c>, <c>@"shell:AppsFolder\windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel"</c>. To discover the string use hotkey Ctrl+Shift+Q or function <see cref="WndUtil.GetWindowsStoreAppId"/> or Google. /// /// Supports environment variables, like <c>@"%TMP%\file.txt"</c>. See <see cref="pathname.expand"/>. /// </remarks> /// <seealso cref="wnd.find"/> /// <seealso cref="wnd.findOrRun"/> /// <seealso cref="wnd.runAndFind"/> /// <example> /// Run Notepad and wait for an active Notepad window. /// <code><![CDATA[ /// run.it("notepad.exe"); /// 1.s(); /// wnd w = wnd.wait(10, true, "*- Notepad", "Notepad"); /// ]]></code> /// Run Notepad or activate a Notepad window. /// <code><![CDATA[ /// wnd w = wnd.findOrRun("*- Notepad", run: () => run.it("notepad.exe")); /// ]]></code> /// Run File Explorer and wait for new folder window. Ignores matching windows that already existed. /// <code><![CDATA[ /// var w = wnd.runAndFind( /// () => run.it(@"explorer.exe"), /// 10, cn: "CabinetWClass"); /// ]]></code> /// </example> public static RResult it(string file, string args = null, RFlags flags = 0, ROptions dirEtc = null) { Api.SHELLEXECUTEINFO x = default; x.cbSize = Api.SizeOf(x); x.fMask = Api.SEE_MASK_NOZONECHECKS | Api.SEE_MASK_NOASYNC | Api.SEE_MASK_CONNECTNETDRV | Api.SEE_MASK_UNICODE; x.nShow = Api.SW_SHOWNORMAL; bool curDirFromFile = false; var more = dirEtc; if (more != null) { x.lpVerb = more.Verb; if (x.lpVerb != null) { x.fMask |= Api.SEE_MASK_INVOKEIDLIST; //makes slower. But verbs are rarely used. } if (more.CurrentDirectory is string cd) { if (cd.Length == 0) { curDirFromFile = true; } else { cd = pathname.expand(cd); } x.lpDirectory = cd; } if (!more.OwnerWindow.IsEmpty) { x.hwnd = more.OwnerWindow.Hwnd.Window; } switch (more.WindowState) { case ProcessWindowStyle.Hidden: x.nShow = Api.SW_HIDE; break; case ProcessWindowStyle.Minimized: x.nShow = Api.SW_SHOWMINIMIZED; break; case ProcessWindowStyle.Maximized: x.nShow = Api.SW_SHOWMAXIMIZED; break; } x.fMask &= ~more.FlagsRemove; x.fMask |= more.FlagsAdd; } if (flags.Has(RFlags.Admin)) { if (x.lpVerb == null || x.lpVerb.Eqi("runas")) { x.lpVerb = "runas"; } else if (!uacInfo.isAdmin) { throw new ArgumentException("Cannot use Verb with flag Admin, unless this process is admin"); } } file = _NormalizeFile(false, file, out bool isFullPath, out bool isShellPath); Pidl pidl = null; if (isShellPath) //":: ITEMIDLIST" or "::{CLSID}..." (we convert it too because the API does not support many) { pidl = Pidl.FromString(file); //does not throw if (pidl != null) { x.lpIDList = pidl.UnsafePtr; x.fMask |= Api.SEE_MASK_INVOKEIDLIST; } else { x.lpFile = file; } } else { x.lpFile = file; if (curDirFromFile && isFullPath) { x.lpDirectory = pathname.getDirectory(file); } } x.lpDirectory ??= Directory.GetCurrentDirectory(); if (!args.NE()) { x.lpParameters = pathname.expand(args); } if (0 == (flags & RFlags.ShowErrorUI)) { x.fMask |= Api.SEE_MASK_FLAG_NO_UI; } if (0 == (flags & RFlags.WaitForExit)) { x.fMask |= Api.SEE_MASK_NO_CONSOLE; } if (0 != (flags & RFlags.MostUsed)) { x.fMask |= Api.SEE_MASK_FLAG_LOG_USAGE; } x.fMask |= Api.SEE_MASK_NOCLOSEPROCESS; WndUtil.EnableActivate(-1); bool waitForExit = 0 != (flags & RFlags.WaitForExit); bool needHandle = flags.Has(RFlags.NeedProcessHandle); bool ok = false; int pid = 0, errorCode = 0; bool asUser = !flags.HasAny(RFlags.Admin | RFlags.InheritAdmin) && uacInfo.isAdmin; //info: new process does not inherit uiAccess if (asUser) { ok = Cpp.Cpp_ShellExec(x, out pid, out int injectError, out int execError); if (!ok) { if (injectError != 0) { print.warning("Failed to run as non-admin."); asUser = false; } else { errorCode = execError; } } } if (!asUser) { ok = Api.ShellExecuteEx(ref x); if (!ok) { errorCode = lastError.code; } } pidl?.Dispose(); if (!ok) { throw new AuException(errorCode, $"*run '{file}'"); } var R = new RResult(); WaitHandle_ ph = null; if (needHandle || waitForExit) { if (pid != 0) { x.hProcess = Handle_.OpenProcess(pid, Api.PROCESS_ALL_ACCESS); } if (!x.hProcess.Is0) { ph = new WaitHandle_(x.hProcess, true); } } if (!waitForExit) { if (pid != 0) { R.ProcessId = pid; } else if (!x.hProcess.Is0) { R.ProcessId = process.processIdFromHandle(x.hProcess); } } try { Api.AllowSetForegroundWindow(); if (x.lpVerb != null && Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { Thread.CurrentThread.Join(50); //need min 5-10 for file Properties. And not Sleep. } if (ph != null) { if (waitForExit) { ph.WaitOne(); if (Api.GetExitCodeProcess(x.hProcess, out var exitCode)) { R.ProcessExitCode = exitCode; } } if (needHandle) { R.ProcessHandle = ph; } } } finally { if (R.ProcessHandle == null) { if (ph != null) { ph.Dispose(); } else { x.hProcess.Dispose(); } } } return(R); //tested: works well in MTA thread. //rejected: in QM2, run also has a 'window' parameter. However it just makes limited, unclear etc, and therefore rarely used. Instead use wnd.findOrRun or Find/Run/Wait like in the examples. //rejected: in QM2, run also has 'autodelay'. Better don't add such hidden things. Let the script decide what to do. }
static unsafe int _RunConsole(Action <string> outAction, StringBuilder outStr, string exe, string args, string curDir, Encoding encoding, bool needLines) { exe = _NormalizeFile(true, exe, out _, out _); //args = pathname.expand(args); //rejected encoding ??= Console.OutputEncoding; //fast. Default is an internal type System.Text.OSEncoding that wraps API GetConsoleOutputCP. var decoder = encoding.GetDecoder(); //ensures we'll not get partial multibyte chars (UTF8 etc) at buffer end/start var ps = new ProcessStarter_(exe, args, curDir, rawExe: true); Handle_ hProcess = default; var sa = new Api.SECURITY_ATTRIBUTES(null) { bInheritHandle = 1 }; if (!Api.CreatePipe(out Handle_ hOutRead, out Handle_ hOutWrite, sa, 0)) { throw new AuException(0); } byte * b = null; //buffer before decoding char * c = null; //buffer after decoding StringBuilder sb = null; //holds part of line when buffer does not end with newline try { Api.SetHandleInformation(hOutRead, 1, 0); //remove HANDLE_FLAG_INHERIT ps.si.dwFlags |= Api.STARTF_USESTDHANDLES | Api.STARTF_USESHOWWINDOW; ps.si.hStdOutput = hOutWrite; ps.si.hStdError = hOutWrite; ps.flags |= Api.CREATE_NEW_CONSOLE; if (!ps.StartL(out var pi, inheritHandles: true)) { throw new AuException(0); } hOutWrite.Dispose(); //important: must be here pi.hThread.Dispose(); hProcess = pi.hProcess; //native console API allows any buffer size when writing, but wrappers usually use small buffer, eg .NET 4-5 KB, msvcrt 5 KB, C++ not tested const int bSize = 8000, cSize = bSize + 10; b = MemoryUtil.Alloc(bSize); for (bool skipN = false; ;) { if (Api.ReadFile(hOutRead, b, bSize, out int nr)) { if (nr == 0) { continue; } } else { if (lastError.code != Api.ERROR_BROKEN_PIPE) { throw new AuException(0); } //process ended if (sb != null && sb.Length > 0) { outAction(sb.ToString()); } break; } if (c == null) { c = MemoryUtil.Alloc <char>(cSize); } int nc = decoder.GetChars(b, nr, c, cSize, false); if (needLines) { var k = new Span <char>(c, nc); if (skipN) { skipN = false; if (c[0] == '\n' && nc > 0) { k = k[1..]; //\r\n split in 2 buffers
/// <summary> /// Adds the action to the queue and notifies the thread to execute it. /// If the thread is busy, returns false; if ifRunning!=0, the action possibly will run later. /// </summary> public bool RunAction(Action actionWrapper, ActionTrigger trigger) { if (_disposed) { return(false); } if (_q == null) { _q = new Queue <_Action>(); _event = Api.CreateEvent(false); try { AThread.Start(() => { try { while (!_disposed && 0 == Api.WaitForSingleObject(_event, -1)) { while (!_disposed) { _Action x; lock (_q) { g1: if (_q.Count == 0) { _running = false; break; } x = _q.Dequeue(); if (x.time != 0 && ATime.PerfMilliseconds > x.time) { goto g1; } _running = true; } x.actionWrapper(); } } } finally { _event.Dispose(); _q = null; _running = false; //restart if aborted //AOutput.Write("thread ended"); } }); } catch (OutOfMemoryException) { //too many threads, probably 32-bit process _event.Dispose(); _OutOfMemory(); } } bool R = true; lock (_q) { int ifRunningWaitMS = trigger.options.ifRunning; if (_running) { if (ifRunningWaitMS == 0) { if (!trigger.options.noWarning) { AOutput.Write("Warning: can't run the trigger action because an action is running in this thread. To run simultaneously or wait, use one of Triggers.Options.RunActionInX functions. To disable this warning: Triggers.Options.RunActionInThread(0, 0, noWarning: true);. Trigger: " + trigger); } return(false); } R = false; } else { _running = true; //if(ifRunningWaitMS > 0 && ifRunningWaitMS < 1000000000) ifRunningWaitMS += 1000; } _q.Enqueue(new _Action { actionWrapper = actionWrapper, time = ifRunningWaitMS <= 0 ? 0 : ATime.PerfMilliseconds + ifRunningWaitMS }); } Api.SetEvent(_event); return(R); }
static unsafe int _RunConsole(Action <string> outAction, StringBuilder outStr, string exe, string args, string curDir, Encoding encoding) { exe = _NormalizeFile(true, exe, out _, out _); //args = APath.ExpandEnvVar(args); //rejected var ps = new ProcessStarter_(exe, args, curDir, rawExe: true); Handle_ hProcess = default; var sa = new Api.SECURITY_ATTRIBUTES(null) { bInheritHandle = 1 }; if (!Api.CreatePipe(out Handle_ hOutRead, out Handle_ hOutWrite, sa, 0)) { throw new AuException(0); } byte *b = null; char *c = null; try { Api.SetHandleInformation(hOutRead, 1, 0); //remove HANDLE_FLAG_INHERIT ps.si.dwFlags |= Api.STARTF_USESTDHANDLES | Api.STARTF_USESHOWWINDOW; ps.si.hStdOutput = hOutWrite; ps.si.hStdError = hOutWrite; ps.flags |= Api.CREATE_NEW_CONSOLE; if (!ps.StartLL(out var pi, inheritHandles: true)) { throw new AuException(0); } hOutWrite.Dispose(); //important: must be here pi.hThread.Dispose(); hProcess = pi.hProcess; //variables for 'prevent getting partial lines' bool needLines = outStr == null /*&& !flags.Has(RCFlags.RawText)*/; int offs = 0; bool skipN = false; int bSize = 8000; b = (byte *)AMemory.Alloc(bSize); for (bool ended = false; !ended;) { if (bSize - offs < 1000) //part of 'prevent getting partial lines' code { b = (byte *)AMemory.ReAlloc(b, bSize *= 2); AMemory.Free(c); c = null; } if (Api.ReadFile(hOutRead, b + offs, bSize - offs, out int nr)) { if (nr == 0) { continue; } nr += offs; } else { if (ALastError.Code != Api.ERROR_BROKEN_PIPE) { throw new AuException(0); } //process ended if (offs == 0) { break; } nr = offs; offs = 0; ended = true; } //prevent getting partial lines. They can be created by the console program, or by the above code when buffer too small. int moveFrom = 0; if (needLines) { if (skipN) //if was split between \r and \n, remove \n now { skipN = false; if (b[0] == '\n') { Api.memmove(b, b + 1, --nr); } if (nr == 0) { continue; } } int i; for (i = nr; i > 0; i--) { var k = b[i - 1]; if (k == '\n' || k == '\r') { break; } } if (i == nr) //ends with \n or \r { offs = 0; if (b[--nr] == '\r') { skipN = true; } else if (nr > 0 && b[nr - 1] == '\r') { nr--; } } else if (i > 0) //contains \n or \r { moveFrom = i; offs = nr - i; if (b[--i] == '\n' && i > 0 && b[i - 1] == '\r') { i--; } nr = i; } else if (!ended) { offs = nr; continue; } } if (c == null) { c = (char *)AMemory.Alloc(bSize * 2); } if (encoding == null) { if ((encoding = s_oemEncoding) == null) { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var oemCP = Api.GetOEMCP(); try { encoding = Encoding.GetEncoding(oemCP); } catch { encoding = Encoding.GetEncoding(437); } s_oemEncoding = encoding; } } int nc = encoding.GetChars(b, nr, c, bSize); if (moveFrom > 0) { Api.memmove(b, b + moveFrom, offs); //part of 'prevent getting partial lines' code } var s = new string(c, 0, nc); if (needLines) { if (s.FindAny("\r\n") < 0) { outAction(s); } else { foreach (var k in s.Segments(SegSep.Line)) { outAction(s[k.start..k.end]);
/// <summary> /// Starts UAC Medium integrity level (IL) process from this admin process. /// </summary> /// <param name="need">Which field to set in <b>Result</b>.</param> /// <exception cref="AuException">Failed.</exception> /// <remarks> /// Actually the process will have the same IL and user session as the shell process (normally explorer). /// Fails if there is no shell process (API GetShellWindow fails) for more than 2 s from calling this func. /// Asserts and fails if this is not admin/system process. Caller should at first call <see cref="AUac.IsAdmin"/> or <see cref="AUac.IntegrityLevel"/>. /// </remarks> internal Result StartUserIL(Result.Need need = 0) { if (s_userToken == null) { Debug.Assert(AUac.IsAdmin); //else cannot set privilege if (!ASecurity.SetPrivilege("SeIncreaseQuotaPrivilege", true)) { goto ge; } //APerf.First(); #if false //works, but slow, eg 60 ms, even if we don't create task everytime var s = $"\"{AFolders.ThisAppBS}{(AVersion.Is32BitProcess ? "32" : "64")}\\AuCpp.dll\",Cpp_RunDll"; WinTaskScheduler.CreateTaskToRunProgramOnDemand("Au", "rundll32", false, AFolders.System + "rundll32.exe", s); //WinTaskScheduler.CreateTaskToRunProgramOnDemand("Au", "rundll32", false, AFolders.System + "notepad.exe"); //slow too //APerf.Next(); int pid = WinTaskScheduler.RunTask("Au", "rundll32"); //APerf.Next(); //AOutput.Write(pid); var hUserProcess = Handle_.OpenProcess(pid); //AOutput.Write((IntPtr)hUserProcess); if (hUserProcess.Is0) { goto ge; } #else bool retry = false; g1: var w = Api.GetShellWindow(); if (w.Is0) //if Explorer process killed or crashed, wait until it restarts { if (!AWaitFor.Condition(2, () => !Api.GetShellWindow().Is0)) { throw new AuException($"*start process '{_exe}' as user. There is no shell process."); } 500.ms(); w = Api.GetShellWindow(); } var hUserProcess = Handle_.OpenProcess(w); if (hUserProcess.Is0) { if (retry) { goto ge; } retry = true; 500.ms(); goto g1; } //two other ways: //1. Enum processes and find one that has Medium IL. Unreliable, eg its token may be modified. //2. Start a service process. Let it start a Medium IL process like in QM2. Because LocalSystem can get token with WTSQueryUserToken. //tested: does not work with GetTokenInformation(TokenLinkedToken). Even if would work, in non-admin session it is wrong token. #endif //APerf.NW(); using (hUserProcess) { if (Api.OpenProcessToken(hUserProcess, Api.TOKEN_DUPLICATE, out Handle_ hShellToken)) { using (hShellToken) { const uint access = Api.TOKEN_QUERY | Api.TOKEN_ASSIGN_PRIMARY | Api.TOKEN_DUPLICATE | Api.TOKEN_ADJUST_DEFAULT | Api.TOKEN_ADJUST_SESSIONID; if (Api.DuplicateTokenEx(hShellToken, access, null, Api.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, Api.TOKEN_TYPE.TokenPrimary, out var userToken)) { s_userToken = new SafeAccessTokenHandle(userToken); } } } } if (s_userToken == null) { goto ge; } } bool suspended = need == Result.Need.NetProcess && !_NetProcessObject.IsFast, resetSuspendedFlag = false; if (suspended && 0 == (flags & Api.CREATE_SUSPENDED)) { flags |= Api.CREATE_SUSPENDED; resetSuspendedFlag = true; } bool ok = Api.CreateProcessWithTokenW(s_userToken.DangerousGetHandle(), 0, null, cl, flags, envVar, curDir, si, out var pi); if (resetSuspendedFlag) { flags &= ~Api.CREATE_SUSPENDED; } if (!ok) { goto ge; } return(new Result(pi, need, suspended)); ge : throw new AuException(0, $"*start process '{_exe}' as user"); }