在MSI安装期间重新启动Explorer.exe不起作用

问题描述 投票:1回答:3

我有一个Visual Studio安装项目,它创建一个MSI来安装我的应用程序。我的应用程序有一个带有图标覆盖处理程序的shell扩展,因此需要重新启动explorer.exe才能使图标覆盖处理程序开始工作。我已经注册了一个自定义操作,以便在使用以下方法重新启动资源管理器的提交上运行:

public static void restartExplorer()
{
    //stop the shell
    try
    {
       Process process1 = new Process();
       ProcessStartInfo startInfo = new ProcessStartInfo();
       startInfo.WindowStyle = ProcessWindowStyle.Hidden;
       startInfo.FileName = "CMD.exe";
       startInfo.Arguments = "/C \"taskkill /f /im explorer.exe\"";
       process1.StartInfo = startInfo;
       process1.Start();
       do
        {
          Thread.Sleep(100);
          if (process1.HasExited)
          {
               break;
          }
         } while (true);
        }
        catch (Exception e)
        {
            Log.Error("restart explorer", e.StackTrace);
        }

        //restart the shell
        string explorer = string.Format("{0}\\{1}", Environment.GetEnvironmentVariable("WINDIR"), "explorer.exe");
        Process process = new Process();
        process.StartInfo.FileName = explorer;
        process.StartInfo.UseShellExecute = true;
        process.Start();

    }

当我从visual studio测试并从命令行调用它时,此方法很有效,但是在安装或卸载期间从MSI调用它时,它会停止资源管理器,但不会重新启动它。

有没有人知道为什么它会在所有情况下都有效,除非在安装或卸载期间由MSI调用?

有没有人有另一种方法在安装/卸载期间从MSI重新启动资源管理器?

installation windows-installer setup-project explorer shell-extensions
3个回答
1
投票

杀死探险家有点粗糙...我建议你使用Restart Manager API。好处是资源管理器知道如何重新启动它,它将在重新启动后恢复所有打开的窗口。这是一个C#实用程序类,它将为您完成。只需在自定义操作中调用此方法:

...
var rm = new RestartManager();
rm.RestartExplorerProcesses();

...

/// <summary>
/// A utility class to restart programs the most gracefully possible. Wraps Windows <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/cc948910.aspx">Restart Manager API</see>. This class cannot be inherited.
/// </summary>
public sealed class RestartManager
{
    /// <summary>
    /// The default kill timeout value (2000).
    /// </summary>
    public const int DefaultKillTimeout = 2000;

    /// <summary>
    /// The default retry count value (10).
    /// </summary>
    public const int DefaultRetryCount = 10;

    /// <summary>
    /// The default retry timeout value (100).
    /// </summary>
    public const int DefaultRetryTimeout = 100;

    /// <summary>
    /// Initializes a new instance of the <see cref="RestartManager"/> class.
    /// </summary>
    public RestartManager()
    {
        KillTimeout = DefaultKillTimeout;
        RetryCount = DefaultRetryCount;
        RetryTimeout = DefaultRetryTimeout;
    }

    /// <summary>
    /// Gets or sets the kill timeout in ms.
    /// </summary>
    /// <value>The kill timeout.</value>
    public int KillTimeout { get; set; }

    /// <summary>
    /// Gets or sets the retry count.
    /// </summary>
    /// <value>The retry count.</value>
    public int RetryCount { get; set; }

    /// <summary>
    /// Gets or sets the retry timeout in ms.
    /// </summary>
    /// <value>The retry timeout.</value>
    public int RetryTimeout { get; set; }

    /// <summary>
    /// Restarts the Windows Explorer processes.
    /// </summary>
    /// <param name="stoppedAction">The stopped action.</param>
    public void RestartExplorerProcesses() => RestartExplorerProcesses(null, false, out var error);

    /// <summary>
    /// Restarts the Windows Explorer processes.
    /// </summary>
    /// <param name="stoppedAction">The stopped action.</param>
    public void RestartExplorerProcesses(ContextCallback stoppedAction) => RestartExplorerProcesses(stoppedAction, false, out var error);

    /// <summary>
    /// Restarts the Windows Explorer processes.
    /// </summary>
    /// <param name="stoppedAction">The stopped action.</param>
    /// <param name="throwOnError">if set to <c>true</c> errors may be throw in case of Windows Restart Manager errors.</param>
    public void RestartExplorerProcesses(ContextCallback stoppedAction, bool throwOnError) => RestartExplorerProcesses(stoppedAction, throwOnError, out var error);

    /// <summary>
    /// Restarts the Windows Explorer processes.
    /// </summary>
    /// <param name="stoppedAction">The stopped action.</param>
    /// <param name="throwOnError">if set to <c>true</c> errors may be throw in case of Windows Restart Manager errors.</param>
    /// <param name="error">The error, if any.</param>
    public void RestartExplorerProcesses(ContextCallback stoppedAction, bool throwOnError, out Exception error)
    {
        var explorers = Process.GetProcessesByName("explorer").Where(p => IsExplorer(p)).ToArray();
        Restart(explorers, stoppedAction, throwOnError, out error);
    }

    /// <summary>
    /// Restarts the processes locking a specific file.
    /// </summary>
    /// <param name="path">The file path.</param>
    /// <param name="stoppedAction">The stopped action.</param>
    /// <param name="throwOnError">if set to <c>true</c> errors may be throw in case of Windows Restart Manager errors.</param>
    /// <param name="error">The error, if any.</param>
    /// <exception cref="ArgumentNullException">path is null.</exception>
    public void RestartProcessesLockingFile(string path, ContextCallback stoppedAction, bool throwOnError, out Exception error)
    {
        if (path == null)
            throw new ArgumentNullException(nameof(path));

        var lockers = GetLockingProcesses(path, false, throwOnError, out error);
        if (error != null)
            return;

        Restart(lockers, stoppedAction, throwOnError, out error);
    }

    /// <summary>
    /// Restarts the Windows Explorer processes locking a specific file.
    /// </summary>
    /// <param name="path">The file path.</param>
    /// <param name="stoppedAction">The stopped action.</param>
    /// <param name="throwOnError">if set to <c>true</c> errors may be throw in case of Windows Restart Manager errors.</param>
    /// <param name="error">The error, if any.</param>
    /// <exception cref="ArgumentNullException">path is null.</exception>
    public void RestartExplorerProcessesLockingFile(string path, ContextCallback stoppedAction, bool throwOnError, out Exception error)
    {
        if (path == null)
            throw new ArgumentNullException(nameof(path));

        var processes = GetLockingProcesses(path, false, throwOnError, out error);
        if (error != null)
            return;

        var explorers = processes.Where(p => IsExplorer(p)).ToArray();
        Restart(explorers, stoppedAction, throwOnError, out error);
    }

    /// <summary>
    /// Determines whether the specified process is Windows Explorer.
    /// </summary>
    /// <param name="process">The process.</param>
    /// <returns><c>true</c> if the specified process is Windows Explorer; otherwise, <c>false</c>.</returns>
    public static bool IsExplorer(Process process)
    {
        if (process == null)
            return false;

        string explorerPath = Path.Combine(Environment.GetEnvironmentVariable("windir"), "explorer.exe");
        return string.Compare(process.MainModule.FileName, explorerPath, StringComparison.OrdinalIgnoreCase) == 0;
    }

    /// <summary>
    /// Gets a list of processes locking a specific file.
    /// </summary>
    /// <param name="filePath">The file path.</param>
    /// <param name="onlyRestartable">if set to <c>true</c> list only restartable processes.</param>
    /// <param name="throwOnError">if set to <c>true</c> errors may be throw in case of Windows Restart Manager errors.</param>
    /// <param name="error">The error, if any.</param>
    /// <returns>A list of processes.</returns>
    /// <exception cref="ArgumentNullException">filePath is null.</exception>
    public IReadOnlyList<Process> GetLockingProcesses(string filePath, bool onlyRestartable, bool throwOnError, out Exception error)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        return GetLockingProcesses(new[] { filePath }, onlyRestartable, throwOnError, out error);
    }

    // NOTE: file name comparison seems to be case insensitive
    /// <summary>
    /// Gets a list of processes locking a list of specific files.
    /// </summary>
    /// <param name="filePaths">The files paths.</param>
    /// <param name="onlyRestartable">if set to <c>true</c> list only restartable processes.</param>
    /// <param name="throwOnError">if set to <c>true</c> errors may be throw in case of Windows Restart Manager errors.</param>
    /// <param name="error">The error, if any.</param>
    /// <returns>A list of processes.</returns>
    /// <exception cref="ArgumentNullException">filePaths is null.</exception>
    public IReadOnlyList<Process> GetLockingProcesses(IEnumerable<string> filePaths, bool onlyRestartable, bool throwOnError, out Exception error)
    {
        if (filePaths == null)
            throw new ArgumentNullException(nameof(filePaths));

        var processes = new List<Process>();
        var paths = new List<string>(filePaths);
        var s = new StringBuilder(256);
        int hr = RmStartSession(out int session, 0, s);
        if (hr != 0)
        {
            error = new Win32Exception(hr);
            if (throwOnError)
                throw error;

            return processes;
        }

        try
        {
            hr = RmRegisterResources(session, paths.Count, paths.ToArray(), 0, null, 0, null);
            if (hr != 0)
            {
                error = new Win32Exception(hr);
                if (throwOnError)
                    throw error;

                return processes;
            }

            int procInfo = 0;
            int rebootReasons = RmRebootReasonNone;
            hr = RmGetList(session, out int procInfoNeeded, ref procInfo, null, ref rebootReasons);
            if (hr == 0)
            {
                error = null;
                return processes;
            }

            if (hr != ERROR_MORE_DATA)
            {
                error = new Win32Exception(hr);
                if (throwOnError)
                    throw error;

                return processes;
            }

            var processInfo = new RM_PROCESS_INFO[procInfoNeeded];
            procInfo = processInfo.Length;

            hr = RmGetList(session, out procInfoNeeded, ref procInfo, processInfo, ref rebootReasons);
            if (hr != 0)
            {
                error = new Win32Exception(hr);
                if (throwOnError)
                    throw error;

                return processes;
            }

            for (int i = 0; i < procInfo; i++)
            {
                try
                {
                    if (processInfo[i].bRestartable || !onlyRestartable)
                    {
                        var process = Process.GetProcessById(processInfo[i].Process.dwProcessId);
                        if (process != null)
                        {
                            processes.Add(process);
                        }
                    }
                }
                catch (Exception e)
                {
                    error = e;
                    // do nothing, fail silently
                    return processes;
                }
            }
            error = null;
            return processes;
        }
        finally
        {
            RmEndSession(session);
        }
    }

    /// <summary>
    /// Restarts the specified processes.
    /// </summary>
    /// <param name="processes">The processes.</param>
    /// <param name="stoppedAction">The stopped action.</param>
    /// <param name="throwOnError">if set to <c>true</c> errors may be throw in case of Windows Restart Manager errors.</param>
    /// <param name="error">The error, if any.</param>
    /// <exception cref="ArgumentNullException">processes is null.</exception>
    public void Restart(IEnumerable<Process> processes, ContextCallback stoppedAction, bool throwOnError, out Exception error)
    {
        if (processes == null)
            throw new ArgumentNullException(nameof(processes));

        if (processes.Count() == 0)
        {
            error = null;
            return;
        }

        var s = new StringBuilder(256);
        int hr = RmStartSession(out int session, 0, s);
        if (hr != 0)
        {
            error = new Win32Exception(hr);
            if (throwOnError)
                throw error;

            return;
        }

        try
        {
            var list = new List<RM_UNIQUE_PROCESS>();
            foreach (var process in processes)
            {
                var p = new RM_UNIQUE_PROCESS()
                {
                    dwProcessId = process.Id
                };

                long l = process.StartTime.ToFileTime();
                p.ProcessStartTime.dwHighDateTime = (int)(l >> 32);
                p.ProcessStartTime.dwLowDateTime = (int)(l & 0xFFFFFFFF);
                list.Add(p);
            }

            hr = RmRegisterResources(session, 0, null, list.Count, list.ToArray(), 0, null);
            if (hr != 0)
            {
                error = new Win32Exception(hr);
                if (throwOnError)
                    throw error;

                return;
            }

            int procInfo = 0;
            int rebootReasons = RmRebootReasonNone;
            hr = RmGetList(session, out int procInfoNeeded, ref procInfo, null, ref rebootReasons);
            if (hr == 0)
            {
                error = null;
                return;
            }

            if (hr != ERROR_MORE_DATA)
            {
                error = new Win32Exception(hr);
                if (throwOnError)
                    throw error;

                return;
            }

            var processInfo = new RM_PROCESS_INFO[procInfoNeeded];
            procInfo = processInfo.Length;

            hr = RmGetList(session, out procInfoNeeded, ref procInfo, processInfo, ref rebootReasons);
            if (hr != 0)
            {
                error = new Win32Exception(hr);
                if (throwOnError)
                    throw error;

                return;
            }

            if (procInfo == 0)
            {
                error = null;
                return;
            }

            bool hasError = false;
            int wtk = GetWaitToKillTimeout();
            var sw = new Stopwatch();
            sw.Start();
            bool finished = false;
            var timer = new Timer((state) =>
            {
                if (!finished)
                {
                    HardKill(processes);
                }
            }, null, wtk + 2000, Timeout.Infinite);

            hr = RmShutdown(session, RmForceShutdown, percent =>
            {
                // add progress info code if needed
            });
            sw.Stop();

            if (hr != 0)
            {
                if (!IsNonFatalError(hr))
                {
                    error = new Win32Exception(hr);
                    if (throwOnError)
                        throw error;

                    return;
                }
                hasError = true;
            }

            if (hasError)
            {
                HardKill(processes);
            }

            if (stoppedAction != null)
            {
                int retry = RetryCount;
                while (retry > 0)
                {
                    try
                    {
                        stoppedAction(session);
                        break;
                    }
                    catch
                    {
                        retry--;
                        Thread.Sleep(RetryTimeout);
                    }
                }
            }

            hr = RmRestart(session, 0, percent2 =>
            {
                // add progress info code if needed
            });

            if (hr != 0)
            {
                error = new Win32Exception(hr);
                if (throwOnError)
                    throw error;

                return;
            }
        }
        finally
        {
            RmEndSession(session);
        }
        error = null;
    }

    private void HardKill(IEnumerable<Process> processes)
    {
        // need a hard restart
        foreach (var process in processes)
        {
            try
            {
                process.Refresh();
                if (!process.HasExited)
                {
                    process.Kill();
                }
            }
            catch
            {
                // do nothing
            }
        }
        Thread.Sleep(KillTimeout);
    }

    private static bool IsNonFatalError(int hr) => hr == ERROR_FAIL_NOACTION_REBOOT || hr == ERROR_FAIL_SHUTDOWN || hr == ERROR_SEM_TIMEOUT || hr == ERROR_CANCELLED;

    /// <summary>
    /// Gets the root Windows Explorer process.
    /// </summary>
    /// <returns>An instance of the Process type or null.</returns>
    public static Process GetRootExplorerProcess()
    {
        Process oldest = null;
        foreach (var process in EnumExplorerProcesses())
        {
            if (oldest == null || process.StartTime < oldest.StartTime)
            {
                oldest = process;
            }
        }
        return oldest;
    }

    /// <summary>
    /// Enumerates Windows Explorer processes.
    /// </summary>
    /// <returns>A list of Windows Explorer processes.</returns>
    public static IEnumerable<Process> EnumExplorerProcesses()
    {
        string explorerPath = Path.Combine(Environment.GetEnvironmentVariable("windir"), "explorer.exe");
        foreach (var process in Process.GetProcessesByName("explorer"))
        {
            if (string.Compare(process.MainModule.FileName, explorerPath, StringComparison.OrdinalIgnoreCase) == 0)
                yield return process;
        }
    }

    /// <summary>
    /// Enumerates a specific process' windows.
    /// </summary>
    /// <param name="process">The process.</param>
    /// <returns>A list of windows handles.</returns>
    /// <exception cref="ArgumentNullException">process is null.</exception>
    public static IReadOnlyList<IntPtr> EnumProcessWindows(Process process)
    {
        if (process == null)
            throw new ArgumentNullException(nameof(process));

        var handles = new List<IntPtr>();
        EnumWindows((h, p) =>
        {
            GetWindowThreadProcessId(h, out int processId);
            if (processId == process.Id)
            {
                handles.Add(h);
            }
            return true;
        }, IntPtr.Zero);
        return handles;
    }

    // https://technet.microsoft.com/en-us/library/cc976045.aspx
    /// <summary>
    /// Gets the wait to kill timeout, that is, how long the system waits for services to stop after notifying the service that the system is shutting down
    /// </summary>
    /// <returns>A number of milliseconds.</returns>
    public static int GetWaitToKillTimeout()
    {
        using (var key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control", false))
        {
            if (key != null)
            {
                var v = key.GetValue("WaitToKillServiceTimeout", 0);
                if (v != null && int.TryParse(v.ToString(), out int i))
                    return i;
            }
            return 0;
        }
    }

    [DllImport("user32.dll")]
    private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);

    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    [DllImport("user32.dll")]
    private static extern bool EnumWindows(EnumWindowsProc callback, IntPtr extraData);

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
    private static extern int RmStartSession(out int pSessionHandle, int dwSessionFlags, StringBuilder strSessionKey);

    [DllImport("rstrtmgr.dll")]
    private static extern int RmEndSession(int pSessionHandle);

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
    private static extern int RmRegisterResources(int pSessionHandle, int nFiles, string[] rgsFilenames, int nApplications, RM_UNIQUE_PROCESS[] rgApplications, int nServices, string[] rgsServiceNames);

    [DllImport("rstrtmgr.dll")]
    private static extern int RmGetList(int dwSessionHandle, out int pnProcInfoNeeded, ref int pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref int lpdwRebootReasons);

    [DllImport("rstrtmgr.dll")]
    private static extern int RmShutdown(int dwSessionHandle, int lActionFlags, StatusHandler fnStatus);

    [DllImport("rstrtmgr.dll")]
    private static extern int RmRestart(int dwSessionHandle, int dwRestartFlags, StatusHandler fnStatus);

    /// <summary>
    /// Represents the method that handles status updates.
    /// </summary>
    /// <param name="percentComplete">The percentage completed.</param>
    public delegate void StatusHandler(int percentComplete);

    private const int RmRebootReasonNone = 0;
    private const int RmForceShutdown = 1;
    private const int RmShutdownOnlyRegistered = 0x10;
    private const int ERROR_MORE_DATA = 234;
    private const int ERROR_FAIL_NOACTION_REBOOT = 350;
    private const int ERROR_FAIL_SHUTDOWN = 351;
    private const int ERROR_SEM_TIMEOUT = 121;
    private const int ERROR_CANCELLED = 1223;
    private const int CCH_RM_MAX_APP_NAME = 255;
    private const int CCH_RM_MAX_SVC_NAME = 63;

    [StructLayout(LayoutKind.Sequential)]
    private struct RM_UNIQUE_PROCESS
    {
        public int dwProcessId;
        public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct RM_PROCESS_INFO
    {
        public RM_UNIQUE_PROCESS Process;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
        public string strAppName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
        public string strServiceShortName;

        public RM_APP_TYPE ApplicationType;
        public RM_APP_STATUS AppStatus;
        public int TSSessionId;

        [MarshalAs(UnmanagedType.Bool)]
        public bool bRestartable;
    }

    [Flags]
    private enum RM_APP_STATUS
    {
        RmStatusUnknown = 0x0,
        RmStatusRunning = 0x1,
        RmStatusStopped = 0x2,
        RmStatusStoppedOther = 0x4,
        RmStatusRestarted = 0x8,
        RmStatusErrorOnStop = 0x10,
        RmStatusErrorOnRestart = 0x20,
        RmStatusShutdownMasked = 0x40,
        RmStatusRestartMasked = 0x80
    }

    private enum RM_APP_TYPE
    {
        RmUnknownApp = 0,
        RmMainWindow = 1,
        RmOtherWindow = 2,
        RmService = 3,
        RmExplorer = 4,
        RmConsole = 5,
        RmCritical = 1000
    }
}

0
投票

我老实说从来没有机会测试这个,但这里有一个快速阅读:Registering Shell Extension Handlers。基本上:你要打电话给SHChangeNotify指定SHCNE_ASSOCCHANGED事件。如果不调用SHChangeNotify,则在重新引导系统之前可能无法识别更改。

github.com上找到了这个:RgssDecrypter - ShellExtension.cs。和another sample


0
投票

将命令更改为

cmd /c taskkill /f /im explorer.exe && start explorer.exe || start explorer.exe

你会发现explorer.exe停止并重新启动

© www.soinside.com 2019 - 2024. All rights reserved.