Exemple #1
0
        //Used by Run and RunConsole.
        static string _NormalizeFile(bool runConsole, string file, out bool isFullPath, out bool isShellPath)
        {
            isShellPath = isFullPath = false;
            file        = APath.ExpandEnvVar(file);
            if (file.NE())
            {
                throw new ArgumentException();
            }
            if (runConsole || !(isShellPath = APath.IsShellPath_(file)))
            {
                if (isFullPath = APath.IsFullPath(file))
                {
                    var fl = runConsole ? PNFlags.DontExpandDosPath : PNFlags.DontExpandDosPath | PNFlags.DontPrefixLongPath;
                    file = APath.Normalize_(file, fl, true);

                    //ShellExecuteEx supports long path prefix for exe but not for documents.
                    //Process.Run supports long path prefix, except when the exe is .NET.
                    if (!runConsole)
                    {
                        file = APath.UnprefixLongPath(file);
                    }

                    if (ADisableFsRedirection.IsSystem64PathIn32BitProcess(file) && !AFile.ExistsAsAny(file))
                    {
                        file = ADisableFsRedirection.GetNonRedirectedSystemPath(file);
                    }
                }
                else if (!APath.IsUrl(file))
                {
                    //ShellExecuteEx searches everywhere except in app folder.
                    //Process.Run prefers current directory.
                    var s2 = AFile.SearchPath(file);
                    if (s2 != null)
                    {
                        file       = s2;
                        isFullPath = true;
                    }
                }
            }
            return(file);
        }
Exemple #2
0
        /// <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.
        }