/// <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. }
/// <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>AFolders.Documents</c> /// - <c>AFolders.System + "notepad.exe"</c> /// - <c>@"%AFolders.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>@"::{CLSID}"</c> /// - <c>@"shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"</c>. /// </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="curDirEtc"> /// 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 ROptions.Verb and RFlags.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>AFolders.Documents</c>, <c>AFolders.System + "notepad.exe"</c>, <c>@"%AFolders.System%\notepad.exe"</c>. /// - Filename of a file or directory, like <c>"notepad.exe"</c>. The function calls <see cref="AFile.SearchPath"/>. /// - Path relative to <see cref="AFolders.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>"http://a.b.c/d"</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="APidl.ToBase64String"/>, <see cref="AFolders.Virtual"/>. Can be used to open virtual folders and items like Control Panel. /// - Shell object's parsing name, like <c>@"::{CLSID}"</c>. See <see cref="APidl.ToShellString"/>, <see cref="AFolders.VirtualPidl"/>. 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 <see cref="AWnd.More.GetWindowsStoreAppId"/> or Google. /// /// Supports environment variables, like <c>@"%TMP%\file.txt"</c>. See <see cref="APath.ExpandEnvVar"/>. /// </remarks> /// <seealso cref="AWnd.FindOrRun"/> /// <example> /// Run notepad and wait for its window. /// <code><![CDATA[ /// AExec.Run("notepad.exe"); /// AWnd w = AWnd.Wait(10, true, "*- Notepad", "Notepad"); /// ]]></code> /// Run notepad or activate its window. /// <code><![CDATA[ /// AWnd w = AWnd.FindOrRun("*- Notepad", run: () => AExec.Run("notepad.exe")); /// ]]></code> /// </example> public static RResult Run(string file, string args = null, RFlags flags = 0, ROptions curDirEtc = null) { //SHOULDDO: from UAC IL admin run as user by default. Add flag UacInherit. Api.SHELLEXECUTEINFO x = default; x.cbSize = Api.SizeOf(x); x.fMask = Api.SEE_MASK_NOZONECHECKS | Api.SEE_MASK_NOASYNC | Api.SEE_MASK_NOCLOSEPROCESS | Api.SEE_MASK_CONNECTNETDRV | Api.SEE_MASK_UNICODE; x.nShow = Api.SW_SHOWNORMAL; bool curDirFromFile = false; var more = curDirEtc; if (more != null) { x.lpVerb = more.Verb; var cd = more.CurrentDirectory; if (cd != null) { if (cd.Length == 0) { curDirFromFile = true; } else { cd = APath.ExpandEnvVar(cd); } } x.lpDirectory = cd; if (!more.OwnerWindow.IsEmpty) { x.hwnd = more.OwnerWindow.Wnd.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; } } if (flags.Has(RFlags.Admin)) { if (more?.Verb != null && !more.Verb.Eqi("runas")) { throw new ArgumentException("Cannot use Verb with flag Admin"); } x.lpVerb = "runas"; } else if (x.lpVerb != null) { x.fMask |= Api.SEE_MASK_INVOKEIDLIST; //makes slower. But verbs are rarely used. } 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; } file = _NormalizeFile(false, file, out bool isFullPath, out bool isShellPath); APidl pidl = null; if (isShellPath) //":: Base64ITEMIDLIST" or "::{CLSID}..." (we convert it too because the API does not support many) { pidl = APidl.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 = APath.GetDirectoryPath(file); } } if (!args.NE()) { x.lpParameters = APath.ExpandEnvVar(args); } AWnd.More.EnableActivate(); if (!Api.ShellExecuteEx(ref x)) { throw new AuException(0, $"*run '{file}'"); } pidl?.Dispose(); var R = new RResult(); bool waitForExit = 0 != (flags & RFlags.WaitForExit); bool needHandle = flags.Has(RFlags.NeedProcessHandle); WaitHandle_ ph = null; if (!x.hProcess.Is0) { if (waitForExit || needHandle) { ph = new WaitHandle_(x.hProcess, true); } if (!waitForExit) { R.ProcessId = AProcess.ProcessIdFromHandle(x.hProcess); } } try { Api.AllowSetForegroundWindow(Api.ASFW_ANY); 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 ec)) { R.ProcessExitCode = ec; } } 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 AWnd.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. }