kayleeFrye_onDeck
kayleeFrye_onDeck

Reputation: 6968

Gracefully restarting explorer.exe as of Windows 10 1709, Fall Creators Update aka Redstone 3

For the current version of Windows 10, 1703, the Creators Update, I have this little C# app getting called to restart explorer.exe during an installation sequence. This is to help refresh the taskbar/registry entries, so that one of the virtual peripherals will appear on the taskbar after installation without rebooting.

using System;
using System.Diagnostics;

namespace RestartExplorer
{
    class Program
    {
        static int Main(string[] args)
        {
            var process = Process.GetProcessesByName("explorer")[0];
            process.Kill();
            Process.Start("explorer");
            return 0;
        }
    }
}

This was working fine in Redstone 2, but in the current Insiders Preview Windows 10 1709 Redstone 3 build of 16294.1.170916-2023, it doesn't just kill the explorer shell, it kills all the open file explorer windows too. That's super invasive, and if I had a couple dozen windows open while working when that happened, I don't think I'd be very happy about the UX.

I verified that CTRL+SHIFTRight-Click on the Taskbar for Exit Explorer also shows the same diverging behavior, not just my little app.

So, if I want to make sure my users' windows aren't all lost, how should I now be restarting explorer, or better yet, is there an even better way to get the end-result for what I'm looking to do?

Upvotes: 1

Views: 1497

Answers (2)

Soroush Falahati
Soroush Falahati

Reputation: 2337

Based on the @Tim code and for providing a more usable example here is the class I just wrote for interacting with Restart Manager:

https://gist.github.com/falahati/34b23831733151460de1368c5fba8e93

Here is an example:

using RestartManager;

public static class Test
{
    public static void Main()
    {
        using (var rm = new RestartManagerSession())
        {
            // add all processes having the name `explorer`
            rm.RegisterProcess(Process.GetProcessesByName("explorer"));

            // you can also add explorer.exe specifically by 
            // using the `RegisterProcessFile()` method
            //rm.RegisterProcessFile(new FileInfo(Path.Combine(
            //  Environment.GetFolderPath(Environment.SpecialFolder.Windows),
            //  "explorer.exe"
            //)));

            rm.Shutdown(RestartManagerSession.ShutdownType.Normal);
            rm.Restart();
        }
    }
}

You can also use the following piece of code to get Explorer's main process in case you want to be more specific:

[DllImport("user32")]
private static extern IntPtr GetShellWindow();

[DllImport("user32", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr windowHandle, out uint processId);

public static Process GetShellProcess()
{
    try
    {
        var shellWindowHandle = GetShellWindow();

        if (shellWindowHandle != IntPtr.Zero)
        {
            GetWindowThreadProcessId(shellWindowHandle, out var shellPid);

            if (shellPid > 0)
            {
                return Process.GetProcessById((int) shellPid);
            }
        }
    }
    catch (Exception)
    {
        // ignored
    }

    return null;
}

And later on, use the same class to restart it with Restart Manager. It is better than Process.GetProcessesByName("explorer")[0] anyway.

Upvotes: 1

Tim
Tim

Reputation: 91

Use the restart manager API to close all open explorers. It will restart any that were closed. Only downside is that restarted apps will be activated so you have to code around your app losing focus.

See https://msdn.microsoft.com/en-us/library/windows/desktop/aa373649(v=vs.85).aspx

var sessionKey = Guid.NewGuid().ToString();
NativeMethods.RmStartSession(out IntPtr session, 0, sessionKey).CheckError();
try
{
    NativeMethods.RmRegisterResources(session, 1, new[] { Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe") }, 0, null, 0, null).CheckError();
    NativeMethods.RmShutdown(session, 0, null).CheckError();
    NativeMethods.RmRestart(session, 0, null).CheckError();
}
finally
{
    NativeMethods.RmEndSession(session);
}

You will also need the following NativeMethods

public static class NativeMethods
{
    [StructLayout(LayoutKind.Sequential)]
    internal struct RM_UNIQUE_PROCESS
    {
        public int dwProcessId;
        public com.FILETIME ProcessStartTime;
    }

    [Flags]
    internal enum RM_SHUTDOWN_TYPE : uint
    {
        RmForceShutdown = 0x1,
        RmShutdownOnlyRegistered = 0x10
    }

    internal delegate void RM_WRITE_STATUS_CALLBACK(UInt32 nPercentComplete);

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
    internal static extern int RmStartSession(out IntPtr pSessionHandle, int dwSessionFlags, string strSessionKey);

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

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

    [DllImport("rstrtmgr.dll")]
    internal static extern int RmShutdown(IntPtr pSessionHandle, RM_SHUTDOWN_TYPE lActionFlags, RM_WRITE_STATUS_CALLBACK fnStatus);

    [DllImport("rstrtmgr.dll")]
    internal static extern int RmRestart(IntPtr pSessionHandle, int dwRestartFlags, RM_WRITE_STATUS_CALLBACK fnStatus);

    [DllImport("kernel32.dll")]
    internal static extern bool GetProcessTimes(IntPtr hProcess, out com.FILETIME lpCreationTime, out com.FILETIME lpExitTime, out com.FILETIME lpKernelTime, out com.FILETIME lpUserTime);
}

Upvotes: 2

Related Questions