Main.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.ComponentModel; 8 using System.Diagnostics; 9 using System.Globalization; 10 using System.IO; 11 using System.IO.Abstractions; 12 using System.Linq; 13 using System.Reflection; 14 using System.Text; 15 using System.Windows.Input; 16 17 using ManagedCommon; 18 using Microsoft.Plugin.Shell.Properties; 19 using Microsoft.PowerToys.Settings.UI.Library; 20 using Wox.Infrastructure.Storage; 21 using Wox.Plugin; 22 using Wox.Plugin.Common; 23 using Wox.Plugin.Logger; 24 25 using Control = System.Windows.Controls.Control; 26 27 namespace Microsoft.Plugin.Shell 28 { 29 public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu, ISavable 30 { 31 private static readonly IFileSystem FileSystem = new FileSystem(); 32 private static readonly IPath Path = FileSystem.Path; 33 private static readonly IFile File = FileSystem.File; 34 private static readonly IDirectory Directory = FileSystem.Directory; 35 36 private readonly ShellPluginSettings _settings; 37 private readonly PluginJsonStorage<ShellPluginSettings> _storage; 38 39 private static readonly CompositeFormat WoxPluginCmdCmdHasBeenExecutedTimes = System.Text.CompositeFormat.Parse(Properties.Resources.wox_plugin_cmd_cmd_has_been_executed_times); 40 41 private string IconPath { get; set; } 42 43 public string Name => Properties.Resources.wox_plugin_cmd_plugin_name; 44 45 public string Description => Properties.Resources.wox_plugin_cmd_plugin_description; 46 47 public static string PluginID => "D409510CD0D2481F853690A07E6DC426"; 48 49 public IEnumerable<PluginAdditionalOption> AdditionalOptions => new List<PluginAdditionalOption>() 50 { 51 new PluginAdditionalOption() 52 { 53 Key = "ShellCommandExecution", 54 DisplayLabel = Resources.wox_shell_command_execution, 55 DisplayDescription = Resources.wox_shell_command_execution_description, 56 PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox, 57 ComboBoxItems = new List<KeyValuePair<string, string>> 58 { 59 new KeyValuePair<string, string>(Resources.find_executable_file_and_run_it, "2"), 60 new KeyValuePair<string, string>(Resources.run_command_in_command_prompt, "0"), 61 new KeyValuePair<string, string>(Resources.run_command_in_powershell, "1"), 62 new KeyValuePair<string, string>(Resources.run_command_in_powershell_seven, "6"), 63 new KeyValuePair<string, string>(Resources.run_command_in_windows_terminal_cmd, "5"), 64 new KeyValuePair<string, string>(Resources.run_command_in_windows_terminal_powershell, "3"), 65 new KeyValuePair<string, string>(Resources.run_command_in_windows_terminal_powershell_seven, "4"), 66 }, 67 ComboBoxValue = (int)_settings.Shell, 68 }, 69 70 new PluginAdditionalOption() 71 { 72 Key = "LeaveShellOpen", 73 DisplayLabel = Resources.wox_leave_shell_open, 74 Value = _settings.LeaveShellOpen, 75 }, 76 }; 77 78 private PluginInitContext _context; 79 private static readonly char[] Separator = new[] { ' ' }; 80 81 public Main() 82 { 83 _storage = new PluginJsonStorage<ShellPluginSettings>(); 84 _settings = _storage.Load(); 85 } 86 87 public void Save() 88 { 89 _storage.Save(); 90 } 91 92 public List<Result> Query(Query query) 93 { 94 ArgumentNullException.ThrowIfNull(query); 95 96 List<Result> results = new List<Result>(); 97 string cmd = query.Search; 98 if (string.IsNullOrEmpty(cmd)) 99 { 100 return ResultsFromHistory(); 101 } 102 else 103 { 104 var queryCmd = GetCurrentCmd(cmd); 105 results.Add(queryCmd); 106 var history = GetHistoryCmds(cmd, queryCmd); 107 results.AddRange(history); 108 109 try 110 { 111 IEnumerable<Result> folderPluginResults = Folder.Main.GetFolderPluginResults(query); 112 results.AddRange(folderPluginResults); 113 } 114 catch (Exception e) 115 { 116 Log.Exception($"Exception when query for <{query}>", e, GetType()); 117 } 118 119 return results; 120 } 121 } 122 123 private List<Result> GetHistoryCmds(string cmd, Result result) 124 { 125 IEnumerable<Result> history = _settings.Count.Where(o => o.Key.Contains(cmd, StringComparison.CurrentCultureIgnoreCase)) 126 .OrderByDescending(o => o.Value) 127 .Select(m => 128 { 129 if (m.Key == cmd) 130 { 131 // Using CurrentCulture since this is user facing 132 result.SubTitle = Properties.Resources.wox_plugin_cmd_plugin_name + ": " + string.Format(CultureInfo.CurrentCulture, WoxPluginCmdCmdHasBeenExecutedTimes, m.Value); 133 return null; 134 } 135 136 var ret = new Result 137 { 138 Title = m.Key, 139 140 // Using CurrentCulture since this is user facing 141 SubTitle = Properties.Resources.wox_plugin_cmd_plugin_name + ": " + string.Format(CultureInfo.CurrentCulture, WoxPluginCmdCmdHasBeenExecutedTimes, m.Value), 142 IcoPath = IconPath, 143 Action = c => 144 { 145 Execute(Process.Start, PrepareProcessStartInfo(m.Key)); 146 return true; 147 }, 148 }; 149 return ret; 150 }).Where(o => o != null).Take(4); 151 return history.ToList(); 152 } 153 154 private Result GetCurrentCmd(string cmd) 155 { 156 Result result = new Result 157 { 158 Title = cmd, 159 Score = 5000, 160 SubTitle = Properties.Resources.wox_plugin_cmd_plugin_name + ": " + Properties.Resources.wox_plugin_cmd_execute_through_shell, 161 IcoPath = IconPath, 162 Action = c => 163 { 164 Execute(Process.Start, PrepareProcessStartInfo(cmd)); 165 return true; 166 }, 167 }; 168 169 return result; 170 } 171 172 private List<Result> ResultsFromHistory() 173 { 174 IEnumerable<Result> history = _settings.Count.OrderByDescending(o => o.Value) 175 .Select(m => new Result 176 { 177 Title = m.Key, 178 179 // Using CurrentCulture since this is user facing 180 SubTitle = Properties.Resources.wox_plugin_cmd_plugin_name + ": " + string.Format(CultureInfo.CurrentCulture, WoxPluginCmdCmdHasBeenExecutedTimes, m.Value), 181 IcoPath = IconPath, 182 Action = c => 183 { 184 Execute(Process.Start, PrepareProcessStartInfo(m.Key)); 185 return true; 186 }, 187 }).Take(5); 188 return history.ToList(); 189 } 190 191 private ProcessStartInfo PrepareProcessStartInfo(string command, RunAsType runAs = RunAsType.None) 192 { 193 string trimmedCommand = command.Trim(); 194 command = Environment.ExpandEnvironmentVariables(trimmedCommand); 195 var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 196 197 // Set runAsArg 198 string runAsVerbArg = string.Empty; 199 if (runAs == RunAsType.OtherUser) 200 { 201 runAsVerbArg = "runAsUser"; 202 } 203 else if (runAs == RunAsType.Administrator || _settings.RunAsAdministrator) 204 { 205 runAsVerbArg = "runAs"; 206 } 207 208 ProcessStartInfo info; 209 if (_settings.Shell == ExecutionShell.Cmd) 210 { 211 var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause"; 212 213 info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsVerbArg); 214 } 215 else if (_settings.Shell == ExecutionShell.Powershell) 216 { 217 string arguments; 218 if (_settings.LeaveShellOpen) 219 { 220 arguments = $"-NoExit \"{command}\""; 221 } 222 else 223 { 224 arguments = $"\"{command} ; Read-Host -Prompt \\\"{Resources.run_plugin_cmd_wait_message}\\\"\""; 225 } 226 227 info = ShellCommand.SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsVerbArg); 228 } 229 else if (_settings.Shell == ExecutionShell.PowerShellSeven) 230 { 231 string arguments; 232 if (_settings.LeaveShellOpen) 233 { 234 arguments = $"-NoExit -C \"{command}\""; 235 } 236 else 237 { 238 arguments = $"-C \"{command} ; Read-Host -Prompt \\\"{Resources.run_plugin_cmd_wait_message}\\\"\""; 239 } 240 241 info = ShellCommand.SetProcessStartInfo("pwsh.exe", workingDirectory, arguments, runAsVerbArg); 242 } 243 else if (_settings.Shell == ExecutionShell.WindowsTerminalCmd) 244 { 245 string arguments; 246 if (_settings.LeaveShellOpen) 247 { 248 arguments = $"cmd.exe /k \"{command}\""; 249 } 250 else 251 { 252 arguments = $"cmd.exe /c \"{command}\" & pause"; 253 } 254 255 info = ShellCommand.SetProcessStartInfo("wt.exe", workingDirectory, arguments, runAsVerbArg); 256 } 257 else if (_settings.Shell == ExecutionShell.WindowsTerminalPowerShell) 258 { 259 string arguments; 260 if (_settings.LeaveShellOpen) 261 { 262 arguments = $"powershell -NoExit -C \"{command}\""; 263 } 264 else 265 { 266 arguments = $"powershell -C \"{command}\""; 267 } 268 269 info = ShellCommand.SetProcessStartInfo("wt.exe", workingDirectory, arguments, runAsVerbArg); 270 } 271 else if (_settings.Shell == ExecutionShell.WindowsTerminalPowerShellSeven) 272 { 273 string arguments; 274 if (_settings.LeaveShellOpen) 275 { 276 arguments = $"pwsh.exe -NoExit -C \"{command}\""; 277 } 278 else 279 { 280 arguments = $"pwsh.exe -C \"{command}\""; 281 } 282 283 info = ShellCommand.SetProcessStartInfo("wt.exe", workingDirectory, arguments, runAsVerbArg); 284 } 285 else if (_settings.Shell == ExecutionShell.RunCommand) 286 { 287 // Open explorer if the path is a file or directory 288 if (Directory.Exists(command) || File.Exists(command)) 289 { 290 info = ShellCommand.SetProcessStartInfo("explorer.exe", arguments: command, verb: runAsVerbArg); 291 } 292 else 293 { 294 var parts = command.Split(Separator, 2); 295 if (parts.Length == 2) 296 { 297 var filename = parts[0]; 298 if (ExistInPath(filename)) 299 { 300 var arguments = parts[1]; 301 if (_settings.LeaveShellOpen) 302 { 303 // Wrap the command in a cmd.exe process 304 info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{filename} {arguments}\"", runAsVerbArg); 305 } 306 else 307 { 308 info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg); 309 } 310 } 311 else 312 { 313 if (_settings.LeaveShellOpen) 314 { 315 // Wrap the command in a cmd.exe process 316 info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg); 317 } 318 else 319 { 320 info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg); 321 } 322 } 323 } 324 else 325 { 326 if (_settings.LeaveShellOpen) 327 { 328 // Wrap the command in a cmd.exe process 329 info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg); 330 } 331 else 332 { 333 info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg); 334 } 335 } 336 } 337 } 338 else 339 { 340 throw new NotImplementedException(); 341 } 342 343 info.UseShellExecute = true; 344 345 _settings.AddCmdHistory(trimmedCommand); 346 347 return info; 348 } 349 350 private enum RunAsType 351 { 352 None, 353 Administrator, 354 OtherUser, 355 } 356 357 private void Execute(Func<ProcessStartInfo, Process> startProcess, ProcessStartInfo info) 358 { 359 try 360 { 361 startProcess(info); 362 } 363 catch (FileNotFoundException e) 364 { 365 var name = "Plugin: " + Properties.Resources.wox_plugin_cmd_plugin_name; 366 var message = $"{Properties.Resources.wox_plugin_cmd_command_not_found}: {e.Message}"; 367 _context.API.ShowMsg(name, message); 368 } 369 catch (Win32Exception e) 370 { 371 var name = "Plugin: " + Properties.Resources.wox_plugin_cmd_plugin_name; 372 var message = $"{Properties.Resources.wox_plugin_cmd_command_failed}: {e.Message}"; 373 _context.API.ShowMsg(name, message); 374 } 375 } 376 377 private static bool ExistInPath(string filename) 378 { 379 if (File.Exists(filename)) 380 { 381 return true; 382 } 383 else 384 { 385 var values = Environment.GetEnvironmentVariable("PATH"); 386 if (values != null) 387 { 388 foreach (var path in values.Split(';')) 389 { 390 var path1 = Path.Combine(path, filename); 391 var path2 = Path.Combine(path, filename + ".exe"); 392 if (File.Exists(path1) || File.Exists(path2)) 393 { 394 return true; 395 } 396 } 397 398 return false; 399 } 400 else 401 { 402 return false; 403 } 404 } 405 } 406 407 public void Init(PluginInitContext context) 408 { 409 this._context = context; 410 _context.API.ThemeChanged += OnThemeChanged; 411 UpdateIconPath(_context.API.GetCurrentTheme()); 412 } 413 414 // Todo : Update with theme based IconPath 415 private void UpdateIconPath(Theme theme) 416 { 417 if (theme == Theme.Light || theme == Theme.HighContrastWhite) 418 { 419 IconPath = "Images/shell.light.png"; 420 } 421 else 422 { 423 IconPath = "Images/shell.dark.png"; 424 } 425 } 426 427 private void OnThemeChanged(Theme currentTheme, Theme newTheme) 428 { 429 UpdateIconPath(newTheme); 430 } 431 432 public Control CreateSettingPanel() 433 { 434 throw new NotImplementedException(); 435 } 436 437 public string GetTranslatedPluginTitle() 438 { 439 return Properties.Resources.wox_plugin_cmd_plugin_name; 440 } 441 442 public string GetTranslatedPluginDescription() 443 { 444 return Properties.Resources.wox_plugin_cmd_plugin_description; 445 } 446 447 public List<ContextMenuResult> LoadContextMenus(Result selectedResult) 448 { 449 var resultList = new List<ContextMenuResult> 450 { 451 new ContextMenuResult 452 { 453 PluginName = Assembly.GetExecutingAssembly().GetName().Name, 454 Title = Properties.Resources.wox_plugin_cmd_run_as_administrator, 455 Glyph = "\xE7EF", 456 FontFamily = "Segoe Fluent Icons,Segoe MDL2 Assets", 457 AcceleratorKey = Key.Enter, 458 AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift, 459 Action = c => 460 { 461 Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, RunAsType.Administrator)); 462 return true; 463 }, 464 }, 465 new ContextMenuResult 466 { 467 PluginName = Assembly.GetExecutingAssembly().GetName().Name, 468 Title = Properties.Resources.wox_plugin_cmd_run_as_user, 469 Glyph = "\xE7EE", 470 FontFamily = "Segoe Fluent Icons,Segoe MDL2 Assets", 471 AcceleratorKey = Key.U, 472 AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift, 473 Action = _ => 474 { 475 Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, RunAsType.OtherUser)); 476 return true; 477 }, 478 }, 479 }; 480 481 return resultList; 482 } 483 484 public void UpdateSettings(PowerLauncherPluginSettings settings) 485 { 486 var leaveShellOpen = false; 487 var shellOption = 2; 488 489 if (settings != null && settings.AdditionalOptions != null) 490 { 491 var optionLeaveShellOpen = settings.AdditionalOptions.FirstOrDefault(x => x.Key == "LeaveShellOpen"); 492 leaveShellOpen = optionLeaveShellOpen?.Value ?? leaveShellOpen; 493 _settings.LeaveShellOpen = leaveShellOpen; 494 495 var optionShell = settings.AdditionalOptions.FirstOrDefault(x => x.Key == "ShellCommandExecution"); 496 shellOption = optionShell?.ComboBoxValue ?? shellOption; 497 _settings.Shell = (ExecutionShell)shellOption; 498 } 499 500 Save(); 501 } 502 } 503 }