行器的實(shí)現(xiàn)原理)
Microsoft PowerToys Run Shell 插件深度解析WinR 式命令執(zhí)行器的實(shí)現(xiàn)原理【免費(fèi)下載鏈接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows項目地址: https://gitcode.com/GitHub_Trending/po/PowerToys本文以 PowerToys 倉庫中 Shell 插件的官方開發(fā)文檔為主線完整解讀這一WinR 仿制版插件的功能設(shè)計——動作關(guān)鍵字、環(huán)境變量展開、執(zhí)行歷史、文件夾導(dǎo)航復(fù)用與 5000 高分機(jī)制并結(jié)合Microsoft.Plugin.Shell插件的源碼逐層剖析七種命令執(zhí)行后端、引號轉(zhuǎn)義規(guī)則、提權(quán)上下文菜單與配置項持久化的實(shí)際實(shí)現(xiàn)幫助讀者掌握在 PowerToys Run 中高效執(zhí)行任意 Windows 命令的用法及其背后的工程細(xì)節(jié)。Shell 插件的定位PowerToys Run 中的WinRShell 插件的核心定位是模擬 Windows 的運(yùn)行對話框WinR用戶在 PowerToys Run 搜索框中輸入加上命令即可執(zhí)行原本要在運(yùn)行框里輸入的內(nèi)容例如ping bing.com、%appdata%。從插件清單 plugin.json 可以看到它的關(guān)鍵元數(shù)據(jù){ 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 }這里有兩個值得注意的設(shè)計點(diǎn)IsGlobal: false表示它是一個非全局non-global插件。PowerToys Run 中全局插件如程序索引會對任何輸入返回結(jié)果而非全局插件必須匹配到指定的動作關(guān)鍵字才會響應(yīng)。Shell 插件的動作關(guān)鍵字就是因此只有輸入以開頭的查詢才會觸發(fā)它。ExecuteFileName指向Microsoft.Plugin.Shell.dll說明它是以 .NET 程序集形式被 Run 主進(jìn)程動態(tài)加載的Main類實(shí)現(xiàn)了IPlugin、IPluginI18n、ISettingProvider、IContextMenu、ISavable等接口分別對應(yīng)查詢、本地化、設(shè)置項、右鍵菜單和配置持久化能力見 Main.cs 第 29 行。官方文檔 shell.md 對該插件的描述可以歸納為五點(diǎn)模擬 WinR、動作為的非全局插件、展開環(huán)境變量、維護(hù)執(zhí)行歷史、復(fù)用 Folder 插件實(shí)現(xiàn)目錄瀏覽。以下逐條對照源碼展開。功能一環(huán)境變量展開文檔指出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);也就是說任何進(jìn)入執(zhí)行流程的命令都會先經(jīng)過Environment.ExpandEnvironmentVariables展開%APPDATA%、%USERPROFILE%等變量隨后工作目錄被固定為用戶主目錄。這與 WinR 的實(shí)際行為一致%appdata%會直接打開C:\Users\用戶名\AppData\Roaming文件夾。值得注意的是歷史記錄統(tǒng)計使用的是展開前的trimmedCommand第 370 行AddCmdHistory(trimmedCommand)保證歷史鍵值穩(wěn)定、不隨環(huán)境變量取值變化而分裂。功能二命令執(zhí)行方式及其演化文檔寫道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 項目最初支持三種執(zhí)行方式命令提示符、PowerShell、運(yùn)行框而 PowerToys Run 版本默認(rèn)始終采用運(yùn)行框語義執(zhí)行命令。從當(dāng)前源碼看這一默認(rèn)語義依然成立且執(zhí)行后端已經(jīng)擴(kuò)展為七種。枚舉定義在 ShellPluginSettings.cspublic enum ExecutionShell { Cmd 0, Powershell 1, RunCommand 2, // 默認(rèn)值 WindowsTerminalPowerShell 3, WindowsTerminalPowerShellSeven 4, WindowsTerminalCmd 5, PowerShellSeven 6, }配合 ShellPluginSettings.cs 中Shell { get; set; } ExecutionShell.RunCommand;的默認(rèn)賦值可以確認(rèn)默認(rèn)執(zhí)行方式就是運(yùn)行框RunCommand。七種后端與設(shè)置界面中的顯示文本對照如下文本來自資源文件 Resources.resx枚舉值設(shè)置界面文本實(shí)際調(diào)用的宿主進(jìn)程Cmd(0)Run in Command Prompt (cmd.exe)cmd.exePowershell(1)Run in PowerShell (PowerShell.exe)powershell.exeRunCommand(2默認(rèn))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設(shè)置界面上還有一條官方說明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 系列的選項都會強(qiáng)制以 Windows Terminal 作為控制臺宿主不受系統(tǒng)默認(rèn)終端設(shè)置影響。執(zhí)行參數(shù)構(gòu)造每種后端的命令行細(xì)節(jié)PrepareProcessStartInfoMain.cs是整個插件最核心的方法它為每種執(zhí)行后端構(gòu)造出最終的ProcessStartInfo。逐分支梳理1. Cmd值 0var arguments _settings.LeaveShellOpen ? $/k {command} : $/c {command} pause; info ShellCommand.SetProcessStartInfo(cmd.exe, workingDirectory, arguments, runAsVerbArg);不保留窗口時拼接 pause讓命令輸出在關(guān)閉窗口前停留保留窗口則用/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掛住進(jìn)程等待回車避免執(zhí)行完立即閃退。3. Windows Terminal 三種組合值 3/4/5統(tǒng)一以wt.exe作為宿主進(jìn)程把具體的 shell 命令作為參數(shù)傳入例如 Windows Terminal PowerShell 分支arguments $powershell -NoExit -C \{escapedPS}\; // 保留窗口 arguments $powershell -C \{escapedPS}\; // 不保留 info ShellCommand.SetProcessStartInfo(wt.exe, workingDirectory, arguments, runAsVerbArg);4. RunCommand值 2默認(rèn)——最貼近 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); // 按第一個空格拆分為 [可執(zhí)行文件, 參數(shù)] if (parts.Length 2) { var filename parts[0]; if (ExistInPath(filename)) // 在 PATH 中查找可執(zhí)行文件 { info ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg); } else { info ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg); } } // ...單段命令的對應(yīng)處理 }這個分支的行為與 WinR 完全對齊輸入一個存在的路徑如%appdata%展開后交給explorer.exe打開輸入可執(zhí)行文件 參數(shù)且可執(zhí)行文件能在PATH中搜到ExistInPath會依次嘗試path\file與path\file.exe則直接以該可執(zhí)行文件啟動進(jìn)程其余情況將整個命令串交給 Shell 執(zhí)行依賴UseShellExecute。所有分支最后統(tǒng)一設(shè)置info.UseShellExecute true第 368 行使Verb字段提權(quán)用生效。引號轉(zhuǎn)義cmd 與 PowerShell 的兩種規(guī)則由于命令串會被拼接進(jìn)外層 shell 的引號中插件實(shí)現(xiàn)了兩套轉(zhuǎn)義函數(shù)Main.cs/// cmd.exe 在雙引號內(nèi)使用 雙寫引號進(jìn)行轉(zhuǎn)義 private static string EscapeCmdArgument(string arg) { return string.IsNullOrEmpty(arg) ? string.Empty : arg.Replace(\, \\); } /// PowerShell 通過 -Command/-C 接收命令串時尊重反斜杠轉(zhuǎn)義 private static string EscapePowerShellArgument(string arg) { return string.IsNullOrEmpty(arg) ? string.Empty : arg.Replace(\, \\\); }這一細(xì)節(jié)解釋了為什么帶引號參數(shù)的命令如%temp% echo hi在兩種宿主下都能被正確執(zhí)行——這是很多從 Wox 繼承的開源實(shí)現(xiàn)容易遺漏的邊界處理。功能三執(zhí)行歷史機(jī)制文檔描述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.歷史數(shù)據(jù)的存儲結(jié)構(gòu)在 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; }即一個命令 → 執(zhí)行次數(shù)的字典每次成功構(gòu)造執(zhí)行信息時對原始未展開的命令計數(shù) 1并隨ISavable接口持久化Save()調(diào)用_storage.Save()。查詢邏輯在Query方法中Main.cs分兩種情況輸入為空只輸入——ResultsFromHistory返回執(zhí)行次數(shù)最高的前 5 條歷史命令I(lǐng)EnumerableResult history _settings.Count.OrderByDescending(o o.Value) .Select(m new Result { Title m.Key, SubTitle ..., Action ... }) .Take(5);輸入非空——結(jié)果按三部分組裝GetCurrentCmd(cmd)當(dāng)前輸入的命令本身副標(biāo)題為 Shell: execute command through command shellGetHistoryCmds對Count字典做不區(qū)分大小寫的子串匹配按次數(shù)降序取前 4 條若某條歷史與當(dāng)前輸入完全相同則不再生成重復(fù)結(jié)果而是把this command has been executed {N} times合并進(jìn)當(dāng)前結(jié)果的副標(biāo)題Folder.Main.GetFolderPluginResults(query)文件夾導(dǎo)航結(jié)果見下文。資源文件中的副標(biāo)題格式串印證了顯示執(zhí)行次數(shù)這一點(diǎn)wox_plugin_cmd_cmd_has_been_executed_timesthis command has been executed {0} times。功能四復(fù)用 Folder 插件實(shí)現(xiàn)目錄瀏覽文檔說明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.對應(yīng)源碼是Query中的這段調(diào)用Main.cstry { IEnumerableResult folderPluginResults Folder.Main.GetFolderPluginResults(query); results.AddRange(folderPluginResults); } catch (Exception e) { Log.Exception($Exception when query for {query}, e, GetType()); }而 Folder 插件側(cè)專門暴露了一個靜態(tài)入口 GetFolderPluginResults內(nèi)部與自身的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); }這正是文檔所說的避免重復(fù)實(shí)現(xiàn)輸入C:\時Shell 插件把查詢轉(zhuǎn)交給 Folder 插件的驅(qū)動器/用戶文件夾處理器返回子目錄列表。另外可以觀察到Folder 結(jié)果進(jìn)入 Shell 結(jié)果集前每個會Score 10AddScore方法使目錄導(dǎo)航結(jié)果在并列打分中略占優(yōu)勢。try/catch包裹也說明設(shè)計者有意保證即使文件夾處理失敗Shell 插件的當(dāng)前命令與歷史結(jié)果依然可用。功能五Score 5000 的高分機(jī)制文檔最后強(qiáng)調(diào)The Shell plugin results have a very high score of 5000. Hence, they are one of the first results in the list.源碼中這條規(guī)則精確落在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 是一個明顯高于常規(guī)插件分值的水位因此帶前綴的立即執(zhí)行條目幾乎總是排在最頂部保證用戶回車命中的就是剛輸入的命令。歷史記錄項與文件夾項不設(shè)置該固定高分因而排在當(dāng)前命令之下。提權(quán)與身份切換右鍵菜單的兩種執(zhí)行身份除默認(rèn)以當(dāng)前用戶執(zhí)行外Shell 插件還通過IContextMenu接口提供了兩個右鍵菜單項Main.cs菜單項快捷鍵行為Run as administratorCtrlShiftEnter以管理員身份執(zhí)行UAC 提權(quán)Run as different userCtrlShiftU以其他用戶身份執(zhí)行實(shí)現(xiàn)路徑是PrepareProcessStartInfo的RunAsType參數(shù)if (runAs RunAsType.OtherUser) runAsVerbArg runAsUser; else if (runAs RunAsType.Administrator || _settings.RunAsAdministrator) runAsVerbArg runAs;由于所有ProcessStartInfo都設(shè)置了UseShellExecute trueVerb字段會觸發(fā) ShellExecute 的runas/runasuser動詞從而彈出 UAC 確認(rèn)框或Windows Security切換用戶對話框。其中其他用戶場景在 ShellCommand.cs 中還有專門的RunAsDifferentUser輔助邏輯它通過枚舉線程窗口輪詢標(biāo)題為 Windows Security 的對話框確保該窗口存在期間宿主進(jìn)程保持等待。此外ShellPluginSettings中保留了RunAsAdministrator開關(guān)ShellPluginSettings.cs一旦為真所有命令都會自動附加runas動詞同文件中的ReplaceWinR字段帶有注釋 not overriding WinR說明產(chǎn)品層面刻意保留了 WinR 快捷鍵給系統(tǒng)本身、不做覆蓋。失敗處理與用戶體驗(yàn)細(xì)節(jié)執(zhí)行動作最終經(jīng)由Execute方法發(fā)出Main.cs其中區(qū)分了兩類異常并向用戶彈出提示FileNotFoundException→ 提示 Command not found: {Message}Win32Exception→ 提示 Error running the command: {Message}。這解釋了文檔截圖中輸入不存在命令時 Run 主窗口的反饋來源。此外插件還訂閱了主題變化事件_context.API.ThemeChanged在淺色/深色/高對比主題間切換shell.light.png與shell.dark.png兩套圖標(biāo)第 440–455 行與 plugin.json 中聲明的IcoPathDark/IcoPathLight保持一致。配置項與設(shè)置持久化Shell 插件通過ISettingProvider接口向 PowerToys Run 的設(shè)置面板暴露兩個可選項Main.cs配置鍵界面標(biāo)簽類型默認(rèn)值作用ShellCommandExecutionCommand execution下拉框7 項值為枚舉字符串2RunCommandFind and run the executable file決定命令在哪個 shell 后端執(zhí)行LeaveShellOpenKeep shell open布爾開關(guān)false執(zhí)行后是否保留控制臺窗口對應(yīng)/k、-NoExit不保留時則拼接 pause/Read-HostUpdateSettings方法負(fù)責(zé)把設(shè)置面板的取值寫回_settings并調(diào)用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(); }可以看到默認(rèn)值在兩端做了兜底即使設(shè)置缺失也回落到RunCommand2與不保留窗口false與ShellPluginSettings的構(gòu)造函數(shù)默認(rèn)值完全一致。實(shí)戰(zhàn)速查結(jié)合文檔與源碼日常使用 Shell 插件的高頻場景%appdata%/%temp%展開環(huán)境變量后由 explorer 打開目錄RunCommand 分支的路徑直達(dá)邏輯ping bing.com直接以 ShellExecute 方式運(yùn)行若選擇 cmd/powershell 后端則能看到輸出窗口不保留窗口時自動 pause/Read-Host 掛起notepad C:\notes.txt首段命中PATH可執(zhí)行文件帶參數(shù)直接啟動僅輸入列出執(zhí)行次數(shù) Top 5 的歷史命令輸入p追加展示前綴匹配的歷史命令及執(zhí)行次數(shù)選中結(jié)果后按 CtrlShiftEnter 可以管理員身份重新執(zhí)行同一命令輸入目錄路徑如C:\借助 Folder 插件列出子目錄進(jìn)行導(dǎo)航。相關(guān)文件索引文件說明doc/devdocs/modules/launcher/plugins/shell.mdShell 插件官方開發(fā)文檔本文主線src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs插件主邏輯查詢、歷史、執(zhí)行構(gòu)造、上下文菜單、設(shè)置同步src/modules/launcher/Plugins/Microsoft.Plugin.Shell/ShellPluginSettings.cs設(shè)置模型ExecutionShell枚舉、歷史計數(shù)字典、LeaveShellOpen等開關(guān)src/modules/launcher/Plugins/Microsoft.Plugin.Shell/plugin.json插件清單ID、動作關(guān)鍵字、非全局標(biāo)識、入口 DLLsrc/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx本地化字符串設(shè)置項文本、executed N times、Press Enter to continue 等src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Main.csGetFolderPluginResults靜態(tài)入口Shell 插件復(fù)用的目錄瀏覽管線src/modules/launcher/Wox.Plugin/Common/ShellCommand.cs進(jìn)程啟動信息構(gòu)造擴(kuò)展與其他用戶執(zhí)行的安全窗口等待邏輯【免費(fèi)下載鏈接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows項目地址: https://gitcode.com/GitHub_Trending/po/PowerToys創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考