
Microsoft PowerToys Run Shell 插件深度解析WinR 式命令执行器的实现原理【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys本文以 PowerToys 仓库中 Shell 插件的官方开发文档为主线完整解读这一WinR 仿制版插件的功能设计——动作关键字、环境变量展开、执行历史、文件夹导航复用与 5000 高分机制并结合Microsoft.Plugin.Shell插件的源码逐层剖析七种命令执行后端、引号转义规则、提权上下文菜单与配置项持久化的实际实现帮助读者掌握在 PowerToys Run 中高效执行任意 Windows 命令的用法及其背后的工程细节。Shell 插件的定位PowerToys Run 中的WinRShell 插件的核心定位是模拟 Windows 的运行对话框WinR用户在 PowerToys Run 搜索框中输入加上命令即可执行原本要在运行框里输入的内容例如ping bing.com、%appdata%。从插件清单 plugin.json 可以看到它的关键元数据{ ID: D409510CD0D2481F853690A07E6DC426, ActionKeyword: , IsGlobal: false, Name: Shell, Author: qianlifeng, Version: 1.0.0, Language: csharp, ExecuteFileName: Microsoft.Plugin.Shell.dll, IcoPathDark: Images\\shell.dark.png, IcoPathLight: Images\\shell.light.png }这里有两个值得注意的设计点IsGlobal: false表示它是一个非全局non-global插件。PowerToys Run 中全局插件如程序索引会对任何输入返回结果而非全局插件必须匹配到指定的动作关键字才会响应。Shell 插件的动作关键字就是因此只有输入以开头的查询才会触发它。ExecuteFileName指向Microsoft.Plugin.Shell.dll说明它是以 .NET 程序集形式被 Run 主进程动态加载的Main类实现了IPlugin、IPluginI18n、ISettingProvider、IContextMenu、ISavable等接口分别对应查询、本地化、设置项、右键菜单和配置持久化能力见 Main.cs 第 29 行。官方文档 shell.md 对该插件的描述可以归纳为五点模拟 WinR、动作为的非全局插件、展开环境变量、维护执行历史、复用 Folder 插件实现目录浏览。以下逐条对照源码展开。功能一环境变量展开文档指出The Shell command expands environment variables, so%appdata%works as expected.这在 Main.cs 的PrepareProcessStartInfo方法开头得到印证string trimmedCommand command.Trim(); command Environment.ExpandEnvironmentVariables(trimmedCommand); var workingDirectory Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);也就是说任何进入执行流程的命令都会先经过Environment.ExpandEnvironmentVariables展开%APPDATA%、%USERPROFILE%等变量随后工作目录被固定为用户主目录。这与 WinR 的实际行为一致%appdata%会直接打开C:\Users\用户名\AppData\Roaming文件夹。值得注意的是历史记录统计使用的是展开前的trimmedCommand第 370 行AddCmdHistory(trimmedCommand)保证历史键值稳定、不随环境变量取值变化而分裂。功能二命令执行方式及其演化文档写道On inheriting the Shell plugin from Wox, there are three different ways of executing a command, using the command prompt, powershell or the run prompt. To uphold the name of PT Run, the Shell plugin always executes commands as the Run prompt would.即该插件继承自 Wox 项目最初支持三种执行方式命令提示符、PowerShell、运行框而 PowerToys Run 版本默认始终采用运行框语义执行命令。从当前源码看这一默认语义依然成立且执行后端已经扩展为七种。枚举定义在 ShellPluginSettings.cspublic enum ExecutionShell { Cmd 0, Powershell 1, RunCommand 2, // 默认值 WindowsTerminalPowerShell 3, WindowsTerminalPowerShellSeven 4, WindowsTerminalCmd 5, PowerShellSeven 6, }配合 ShellPluginSettings.cs 中Shell { get; set; } ExecutionShell.RunCommand;的默认赋值可以确认默认执行方式就是运行框RunCommand。七种后端与设置界面中的显示文本对照如下文本来自资源文件 Resources.resx枚举值设置界面文本实际调用的宿主进程Cmd(0)Run in Command Prompt (cmd.exe)cmd.exePowershell(1)Run in PowerShell (PowerShell.exe)powershell.exeRunCommand(2默认)Find and run the executable file直接 ShellExecute 命令本身WindowsTerminalPowerShell(3)Run in PowerShell using Windows Terminalwt.exepowershellWindowsTerminalPowerShellSeven(4)Run in PowerShell 7 using Windows Terminalwt.exepwsh.exeWindowsTerminalCmd(5)Run in Command Prompt using Windows Terminalwt.execmd.exePowerShellSeven(6)Run in PowerShell 7 (pwsh.exe)pwsh.exe设置界面上还有一条官方说明resx 中的wox_shell_command_execution_descriptionAll entries using the Windows Terminal force the Windows Terminal as the console host regardless of the system settings——即凡是选择 Windows Terminal 系列的选项都会强制以 Windows Terminal 作为控制台宿主不受系统默认终端设置影响。执行参数构造每种后端的命令行细节PrepareProcessStartInfoMain.cs是整个插件最核心的方法它为每种执行后端构造出最终的ProcessStartInfo。逐分支梳理1. Cmd值 0var arguments _settings.LeaveShellOpen ? $/k {command} : $/c {command} pause; info ShellCommand.SetProcessStartInfo(cmd.exe, workingDirectory, arguments, runAsVerbArg);不保留窗口时拼接 pause让命令输出在关闭窗口前停留保留窗口则用/k。2. PowerShell值 1/ PowerShell 7值 6string escapedPS EscapePowerShellArgument(command); // 不保留-C cmd ; Read-Host -Prompt \Press Enter to continue\ // 保留 -NoExit -C cmd两种模式都通过-C-Command的简写传入命令串不保留窗口时用Read-Host挂住进程等待回车避免执行完立即闪退。3. Windows Terminal 三种组合值 3/4/5统一以wt.exe作为宿主进程把具体的 shell 命令作为参数传入例如 Windows Terminal PowerShell 分支arguments $powershell -NoExit -C \{escapedPS}\; // 保留窗口 arguments $powershell -C \{escapedPS}\; // 不保留 info ShellCommand.SetProcessStartInfo(wt.exe, workingDirectory, arguments, runAsVerbArg);4. RunCommand值 2默认——最贴近 WinR 语义的分支// 若命令是已存在的文件/目录路径直接交给 explorer.exe 打开 if (Directory.Exists(command) || File.Exists(command)) { info ShellCommand.SetProcessStartInfo(explorer.exe, arguments: command, verb: runAsVerbArg); } else { var parts command.Split(Separator, 2); // 按第一个空格拆分为 [可执行文件, 参数] if (parts.Length 2) { var filename parts[0]; if (ExistInPath(filename)) // 在 PATH 中查找可执行文件 { info ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg); } else { info ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg); } } // ...单段命令的对应处理 }这个分支的行为与 WinR 完全对齐输入一个存在的路径如%appdata%展开后交给explorer.exe打开输入可执行文件 参数且可执行文件能在PATH中搜到ExistInPath会依次尝试path\file与path\file.exe则直接以该可执行文件启动进程其余情况将整个命令串交给 Shell 执行依赖UseShellExecute。所有分支最后统一设置info.UseShellExecute true第 368 行使Verb字段提权用生效。引号转义cmd 与 PowerShell 的两种规则由于命令串会被拼接进外层 shell 的引号中插件实现了两套转义函数Main.cs/// cmd.exe 在双引号内使用 双写引号进行转义 private static string EscapeCmdArgument(string arg) { return string.IsNullOrEmpty(arg) ? string.Empty : arg.Replace(\, \\); } /// PowerShell 通过 -Command/-C 接收命令串时尊重反斜杠转义 private static string EscapePowerShellArgument(string arg) { return string.IsNullOrEmpty(arg) ? string.Empty : arg.Replace(\, \\\); }这一细节解释了为什么带引号参数的命令如%temp% echo hi在两种宿主下都能被正确执行——这是很多从 Wox 继承的开源实现容易遗漏的边界处理。功能三执行历史机制文档描述The Shell plugin has a concept of history where the previously executed commands show up in the drop down list along with the number of times they have been executed.历史数据的存储结构在 ShellPluginSettings.cspublic Dictionarystring, int Count { get; } new Dictionarystring, int(); public void AddCmdHistory(string cmdName) { if (Count.TryGetValue(cmdName, out int currentCount)) Count[cmdName] currentCount 1; else Count[cmdName] 1; }即一个命令 → 执行次数的字典每次成功构造执行信息时对原始未展开的命令计数 1并随ISavable接口持久化Save()调用_storage.Save()。查询逻辑在Query方法中Main.cs分两种情况输入为空只输入——ResultsFromHistory返回执行次数最高的前 5 条历史命令IEnumerableResult history _settings.Count.OrderByDescending(o o.Value) .Select(m new Result { Title m.Key, SubTitle ..., Action ... }) .Take(5);输入非空——结果按三部分组装GetCurrentCmd(cmd)当前输入的命令本身副标题为 Shell: execute command through command shellGetHistoryCmds对Count字典做不区分大小写的子串匹配按次数降序取前 4 条若某条历史与当前输入完全相同则不再生成重复结果而是把this command has been executed {N} times合并进当前结果的副标题Folder.Main.GetFolderPluginResults(query)文件夹导航结果见下文。资源文件中的副标题格式串印证了显示执行次数这一点wox_plugin_cmd_cmd_has_been_executed_timesthis command has been executed {0} times。功能四复用 Folder 插件实现目录浏览文档说明The Run prompt has the folder plugin function where we can navigate to different locations and entering the path to a directory displays all the sub-directories. To prevent reimplementing this logic, the shell plugin references the folder plugin to implement this functionality.对应源码是Query中的这段调用Main.cstry { IEnumerableResult folderPluginResults Folder.Main.GetFolderPluginResults(query); results.AddRange(folderPluginResults); } catch (Exception e) { Log.Exception($Exception when query for {query}, e, GetType()); }而 Folder 插件侧专门暴露了一个静态入口 GetFolderPluginResults内部与自身的Query走同一套处理器管线public static IEnumerableResult GetFolderPluginResults(Query query) { var expandedName FolderHelper.Expand(query.Search); return _processors.SelectMany(processor processor.Results(query.ActionKeyword, expandedName)) .Select(res res.Create(_context.API)) .Select(AddScore); }这正是文档所说的避免重复实现输入C:\时Shell 插件把查询转交给 Folder 插件的驱动器/用户文件夹处理器返回子目录列表。另外可以观察到Folder 结果进入 Shell 结果集前每个会Score 10AddScore方法使目录导航结果在并列打分中略占优势。try/catch包裹也说明设计者有意保证即使文件夹处理失败Shell 插件的当前命令与历史结果依然可用。功能五Score 5000 的高分机制文档最后强调The Shell plugin results have a very high score of 5000. Hence, they are one of the first results in the list.源码中这条规则精确落在GetCurrentCmd方法里Main.csprivate Result GetCurrentCmd(string cmd) { Result result new Result { Title cmd, Score 5000, SubTitle ..., IcoPath IconPath, Action c { Execute(Process.Start, PrepareProcessStartInfo(cmd)); return true; }, }; return result; }PowerToys Run 的候选列表按Score降序展示5000 是一个明显高于常规插件分值的水位因此带前缀的立即执行条目几乎总是排在最顶部保证用户回车命中的就是刚输入的命令。历史记录项与文件夹项不设置该固定高分因而排在当前命令之下。提权与身份切换右键菜单的两种执行身份除默认以当前用户执行外Shell 插件还通过IContextMenu接口提供了两个右键菜单项Main.cs菜单项快捷键行为Run as administratorCtrlShiftEnter以管理员身份执行UAC 提权Run as different userCtrlShiftU以其他用户身份执行实现路径是PrepareProcessStartInfo的RunAsType参数if (runAs RunAsType.OtherUser) runAsVerbArg runAsUser; else if (runAs RunAsType.Administrator || _settings.RunAsAdministrator) runAsVerbArg runAs;由于所有ProcessStartInfo都设置了UseShellExecute trueVerb字段会触发 ShellExecute 的runas/runasuser动词从而弹出 UAC 确认框或Windows Security切换用户对话框。其中其他用户场景在 ShellCommand.cs 中还有专门的RunAsDifferentUser辅助逻辑它通过枚举线程窗口轮询标题为 Windows Security 的对话框确保该窗口存在期间宿主进程保持等待。此外ShellPluginSettings中保留了RunAsAdministrator开关ShellPluginSettings.cs一旦为真所有命令都会自动附加runas动词同文件中的ReplaceWinR字段带有注释 not overriding WinR说明产品层面刻意保留了 WinR 快捷键给系统本身、不做覆盖。失败处理与用户体验细节执行动作最终经由Execute方法发出Main.cs其中区分了两类异常并向用户弹出提示FileNotFoundException→ 提示 Command not found: {Message}Win32Exception→ 提示 Error running the command: {Message}。这解释了文档截图中输入不存在命令时 Run 主窗口的反馈来源。此外插件还订阅了主题变化事件_context.API.ThemeChanged在浅色/深色/高对比主题间切换shell.light.png与shell.dark.png两套图标第 440–455 行与 plugin.json 中声明的IcoPathDark/IcoPathLight保持一致。配置项与设置持久化Shell 插件通过ISettingProvider接口向 PowerToys Run 的设置面板暴露两个可选项Main.cs配置键界面标签类型默认值作用ShellCommandExecutionCommand execution下拉框7 项值为枚举字符串2RunCommandFind and run the executable file决定命令在哪个 shell 后端执行LeaveShellOpenKeep shell open布尔开关false执行后是否保留控制台窗口对应/k、-NoExit不保留时则拼接 pause/Read-HostUpdateSettings方法负责把设置面板的取值写回_settings并调用Save()落盘Main.cspublic void UpdateSettings(PowerLauncherPluginSettings settings) { var leaveShellOpen false; var shellOption 2; if (settings ! null settings.AdditionalOptions ! null) { var optionLeaveShellOpen settings.AdditionalOptions.FirstOrDefault(x x.Key LeaveShellOpen); leaveShellOpen optionLeaveShellOpen?.Value ?? leaveShellOpen; _settings.LeaveShellOpen leaveShellOpen; var optionShell settings.AdditionalOptions.FirstOrDefault(x x.Key ShellCommandExecution); shellOption optionShell?.ComboBoxValue ?? shellOption; _settings.Shell (ExecutionShell)shellOption; } Save(); }可以看到默认值在两端做了兜底即使设置缺失也回落到RunCommand2与不保留窗口false与ShellPluginSettings的构造函数默认值完全一致。实战速查结合文档与源码日常使用 Shell 插件的高频场景%appdata%/%temp%展开环境变量后由 explorer 打开目录RunCommand 分支的路径直达逻辑ping bing.com直接以 ShellExecute 方式运行若选择 cmd/powershell 后端则能看到输出窗口不保留窗口时自动 pause/Read-Host 挂起notepad C:\notes.txt首段命中PATH可执行文件带参数直接启动仅输入列出执行次数 Top 5 的历史命令输入p追加展示前缀匹配的历史命令及执行次数选中结果后按 CtrlShiftEnter 可以管理员身份重新执行同一命令输入目录路径如C:\借助 Folder 插件列出子目录进行导航。相关文件索引文件说明doc/devdocs/modules/launcher/plugins/shell.mdShell 插件官方开发文档本文主线src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs插件主逻辑查询、历史、执行构造、上下文菜单、设置同步src/modules/launcher/Plugins/Microsoft.Plugin.Shell/ShellPluginSettings.cs设置模型ExecutionShell枚举、历史计数字典、LeaveShellOpen等开关src/modules/launcher/Plugins/Microsoft.Plugin.Shell/plugin.json插件清单ID、动作关键字、非全局标识、入口 DLLsrc/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx本地化字符串设置项文本、executed N times、Press Enter to continue 等src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Main.csGetFolderPluginResults静态入口Shell 插件复用的目录浏览管线src/modules/launcher/Wox.Plugin/Common/ShellCommand.cs进程启动信息构造扩展与其他用户执行的安全窗口等待逻辑【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考