Example #1
0
		public ShellItem()
		{
			_rootItem = this;
			try
			{
				// create a PIDL for the Desktop shell item.
				_pidl = new Pidl(Environment.SpecialFolder.Desktop);

				var shInfo = new Native.SHFILEINFO();
				Native.Shell32.SHGetFileInfo((IntPtr) _pidl, 0, out shInfo, (uint) Marshal.SizeOf(shInfo), SHGFI.SHGFI_PIDL | SHGFI.SHGFI_DISPLAYNAME | SHGFI.SHGFI_SYSICONINDEX);

				// get the root IShellFolder interface
				int hResult = Native.Shell32.SHGetDesktopFolder(ref _shellFolder);
				if (hResult != 0)
					Marshal.ThrowExceptionForHR(hResult);

				_displayName = shInfo.szDisplayName;
				_typeName = string.Empty;
				_iconIndex = shInfo.iIcon;
				_isFolder = true;
				_isVirtual = true;
				_hasSubFolders = true;
			}
			catch (Exception ex)
			{
				// if an exception happens during construction, we must release the PIDL now (remember, it's a pointer!)
				if (_pidl != null)
					_pidl.Dispose();
				throw new Exception("Creation of the root namespace shell item failed.", ex);
			}

#if DEBUG
			_instanceCount++;
#endif
		}
Example #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>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.
        }