windows 获取当前 explorer 打开的目录路径

需求

因为 windows 11 的 explorer 经常黑屏崩溃,导致刚才打开的文件夹还要再次手动打开,非常麻烦,于是希望编写程序记录当前 explorer 打开的目录路径,方便下次打开。

关于方案,AI 给出的代码都无法获得当前打开的目录路径,在中文互联网上搜索,也只能得到诸如“这是什么需求”“你得知道拿到路径后要干什么才能拿到路径”之类的无用信息。

解决方案

既然绑定 windows 了,考虑 Powershell。在微软社区 Hey, Scripting Guy! How Can I Use Windows PowerShell to Get a List of All the Open Windows on a Computer? 中找到一个解决方案

1
(New-Object -ComObject "Shell.Application").Windows() | Select-Object -ExpandProperty LocationURL

解释:

New-Object -ComObject "Shell.Application"

  • New-Object 是 PowerShell 的一个 cmdlet,用于创建新的对象。
  • -ComObject 参数表示创建一个 COM 对象。
  • "Shell.Application" 是一个 COM 组件,用于与 Windows Shell 进行交互。这个组件可以访问和操作 Windows 资源管理器窗口、文件夹、文件等。

.Windows()

  • 这是 Shell.Application 对象的一个方法,用于获取当前所有打开的 Windows 资源管理器窗口的集合。它返回一个包含所有资源管理器窗口的对象数组。

| Select-Object -ExpandProperty LocationURL

  • | 是管道符,用于将前一个命令的输出传递给下一个命令。
  • Select-Object 是 PowerShell 的一个 cmdlet,用于选择对象的属性。
  • -ExpandProperty LocationURL 表示从每个对象中提取 LocationURL 属性的值。
    • LocationURL 是资源管理器窗口的一个属性,表示当前窗口的路径,格式为 URL 样式(例如:file:///C:/Users/username/Desktop)。

读取输出

现在在程序里面读取输出,然后切掉 file:/// 前缀,就可以得到当前打开的目录路径了。

出了添加了前缀,输出的路径还是 URL 编码后的,所以还要解码。

为了方便读取,我用的正则表达式

1
file:\/\/\/(.+)\b

解释:

  • file:\/\/\/ 表示匹配 file:///
  • (.+)\b 匹配任意字符,直到遇到单词边界,在这个例子里面,由于路径被 URL 转义,保证了路径不含空格,边界就是换行符了。

C# 获取所有打开的文件夹路径的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
namespace RestoreOpenedExplorer
{
internal class ExplorerDirectoryRetriever
{
// 用于匹配file协议URL的正则表达式,捕获file:///之后的路径部分
private static Regex fileRegex = new(@"file:\/\/\/(.+)\b", RegexOptions.Compiled);

/// <summary>
/// 获取当前所有打开的资源管理器窗口的目录路径
/// </summary>
/// <returns>包含所有打开目录路径的列表</returns>
public static List<string> GetOpenExplorerDirectories()
{
List<string> directories = new List<string>();

// PowerShell命令:获取所有Shell应用程序窗口的LocationURL属性(即文件路径的URL格式)
string pwsh = @"(New-Object -ComObject ""Shell.Application"").Windows() | Select-Object -ExpandProperty LocationURL";

// 配置PowerShell进程启动信息
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = "-Command " + pwsh,
UseShellExecute = false, // 不使用系统shell启动
RedirectStandardOutput = true, // 重定向标准输出以便获取命令结果
CreateNoWindow = true // 不创建可见窗口
};

// 启动PowerShell进程并获取输出
Process? p = Process.Start(psi);
if (p == null)
{
Debug.WriteLine("Failed to start powershell.exe");
return directories;
}
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Debug.WriteLine(output);

// 使用正则表达式匹配所有file:///格式的URL
MatchCollection matches = fileRegex.Matches(output);
foreach (Match match in matches)
{
string directory = match.Groups[1].Value; // 获取捕获组中的路径部分
if (directory.Length > 0)
{
// 处理URL编码(例如将%20转换为空格)并去除尾部空白字符
directory = Uri.UnescapeDataString(directory).TrimEnd();

// 验证目录是否实际存在(过滤无效路径)
if (!Directory.Exists(directory))
{
Debug.WriteLine("Directory does not exist: " + directory);
continue;
}
directories.Add(directory);
}
}
return directories;
}
}
}

附:利用键盘钩子监听快捷键

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows;
using System.Diagnostics;

namespace QuickPopupSnippet
{
public class GlobalHotkey
{
const int WM_HOTKEY = 0x312;
static int keyid = 10;
static Dictionary<int, HotKeyCallBackHanlder> keymap = new();

public delegate void HotKeyCallBackHanlder();

#region API

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

[DllImport("user32.dll")]
static extern bool UnregisterHotKey(IntPtr hWnd, int id);

#endregion

/// <summary>
/// 注册快捷键
/// </summary>
/// <param name="window">持有快捷键窗口</param>
/// <param name="fsModifiers">组合键</param>
/// <param name="key">快捷键</param>
/// <param name="callBack">回调函数</param>
public static void Regist(
Window window,
uint fsModifiers,
Key key,
HotKeyCallBackHanlder callBack
)
{
var hwnd = new WindowInteropHelper(window).Handle;
var _hwndSource = HwndSource.FromHwnd(hwnd);
_hwndSource.AddHook(WndProc);

int id = keyid++;

var vk = KeyInterop.VirtualKeyFromKey(key);
if (!RegisterHotKey(hwnd, id, fsModifiers, (uint)vk))
throw new Exception("regist hotkey fail.");
keymap[id] = callBack;
}

/// <summary>
/// 快捷键消息处理
/// </summary>
static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_HOTKEY)
{
int id = wParam.ToInt32();
if (keymap.TryGetValue(id, out var callback))
{
callback();
}
}
return IntPtr.Zero;
}

/// <summary>
/// 注销快捷键
/// </summary>
/// <param name="hWnd">持有快捷键窗口的句柄</param>
/// <param name="callBack">回调函数</param>
public static void UnRegist(IntPtr hWnd, HotKeyCallBackHanlder callBack)
{
foreach (KeyValuePair<int, HotKeyCallBackHanlder> var in keymap)
{
if (var.Value == callBack)
UnregisterHotKey(hWnd, var.Key);
}
}

/// <summary>
/// 注销快捷键
/// </summary>
/// <param name="hWnd">持有快捷键窗口的句柄</param>
/// <param name="callBack">回调函数</param>
public static void UnRegistNoChecking(IntPtr hWnd)
{
foreach (KeyValuePair<int, HotKeyCallBackHanlder> var in keymap)
{
UnregisterHotKey(hWnd, var.Key);
}
}

/// <summary>
/// 注销快捷键
/// </summary>
/// <param name="hWnd">持有快捷键窗口的句柄</param>
/// <param name="callBack">回调函数</param>
public static void UnRegistNoChecking(Window window)
{
var hwnd = new WindowInteropHelper(window).Handle;
foreach (KeyValuePair<int, HotKeyCallBackHanlder> var in keymap)
{
UnregisterHotKey(hwnd, var.Key);
}
}

public enum HotkeyModifiers
{
Alt = 0x1,
Ctrl = 0x2,
Shift = 0x4,
Win = 0x8
}

static Dictionary<string, HotkeyModifiers> STR_HOTKEY_MAP =
new()
{
{ "Alt", HotkeyModifiers.Alt },
{ "alt", HotkeyModifiers.Alt },
{ "Ctrl", HotkeyModifiers.Ctrl },
{ "ctrl", HotkeyModifiers.Ctrl },
{ "Control", HotkeyModifiers.Ctrl },
{ "control", HotkeyModifiers.Ctrl },
{ "Shift", HotkeyModifiers.Shift },
{ "shift", HotkeyModifiers.Shift },
{ "Win", HotkeyModifiers.Win },
{ "win", HotkeyModifiers.Win },
{ "windows", HotkeyModifiers.Win },
{ "Windows", HotkeyModifiers.Win }
};

/// <summary>
/// parse the key modifiers from string[] like {"Ctrl", "Shift"}
/// </summary>
/// <param name="texts"></param>
/// <returns></returns>
public static uint GetModifiersFromStrings(string[] texts)
{
uint result = 0x0;
foreach (var key in texts)
{
if (STR_HOTKEY_MAP.TryGetValue(key, out var value))
{
result |= (uint)value;
Debug.WriteLine($"hotkey modifier: {key} parsed as {value}");
}
}
Debug.WriteLine($"hotkey modifier: final is {result}");
return result;
}

public static Key GetKeyFromString(string key)
{
return (Key)Enum.Parse(typeof(Key), key, true);
}
}
}

利用一个 json 文件存储配置好的快捷键:

1
2
3
4
5
6
{
"hotkey": {
"modifier": [ "alt" ],
"key": "f"
},
}

json 被解析为 C# 实体类,名称是 _config

使用方法(对于 WPF 程序)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected override void OnSourceInitialized(EventArgs e)
{
if (
_config is null
|| _config.hotkey is null
|| _config.hotkey.modifier is null
|| _config.hotkey.key is null
)
{
MessageBox.Show("missing config key: config.hotkey.(modifier|key)");
return;
}
try
{
GlobalHotkey.Regist(
this,
GlobalHotkey.GetModifiersFromStrings(_config.hotkey.modifier.ToArray()),
GlobalHotkey.GetKeyFromString(_config.hotkey.key),
() =>
{
Debug.WriteLine("hotkey");
// do something
}
);
}
catch (Exception ex)
{
MessageBox.Show($"cannot register hotkey: {ex.Message}");
}
}

windows 获取当前 explorer 打开的目录路径
https://taylorandtony.github.io/2025/07/08/windows-获取当前-explorer-打开的目录路径/
作者
TaylorAndTony
发布于
2025年7月8日
许可协议