diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index 4e216b7b26a..35c0418f3ab 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -55,6 +55,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs index be2a2dd6673..35191b6e0fe 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Flow.Launcher.Infrastructure; @@ -63,7 +63,7 @@ public List LoadContextMenus(Result result) private List CreateResultsFromQuery(Query query) { string termToSearch = query.Search; - var processlist = processHelper.GetMatchingProcesses(termToSearch); + var processlist = processHelper.GetMatchingProcesses(termToSearch, _context); if (!processlist.Any()) { @@ -79,7 +79,7 @@ private List CreateResultsFromQuery(Query query) results.Add(new Result() { IcoPath = path, - Title = p.ProcessName + " - " + p.Id, + Title = $"{p.ProcessName} - {p.Id}" + (pr.Port != 0 ? $" - [{pr.Port}]" : ""), SubTitle = path, TitleHighlightData = StringMatcher.FuzzySearch(termToSearch, p.ProcessName).MatchData, Score = pr.Score, diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/PortHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/PortHelper.cs new file mode 100644 index 00000000000..08359f84b79 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/PortHelper.cs @@ -0,0 +1,168 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Management; + +namespace Flow.Launcher.Plugin.ProcessKiller +{ + internal class PortDetail + { + public int Port { get; set; } + public int ProcessID { get; set; } + public string ProcessName { get; set; } + public Process Process { get; set; } + public string Path { get; set; } + + public override string ToString() + { + return $@" Process Name: {ProcessName}, Process ID: {ProcessID}, Port: {Port}, Path : {Path}"; + } + } + + /// + /// Usage: + /// int port = 8081 + /// TcpHelperUtil tcpHelper = new TcpHelperUtil(); + /// var details = tcpHelper.GetPortDetails(port); + /// if (details.Item1) + /// { + /// Console.WriteLine("Port {0} in Use",port); + /// Console.WriteLine(details.Item2.ToString()); + /// }else + /// { + /// Console.WriteLine("Port {0} is free ",port); + /// } + /// + internal class PortHelper + { + private const short MINIMUM_TOKEN_IN_A_LINE = 5; + private const string COMMAND_EXE = "cmd"; + + private static string ClassName => nameof(PortHelper); + + public static (bool Result, PortDetail Detail) GetPortDetails(int port, PluginInitContext context) + { + var portDetail = new PortDetail(); + + // execute netstat command for the given port + string commandArgument = string.Format("/c netstat -an -o -p tcp|findstr \":{0}.*LISTENING\"", port); + + string commandOut = ExecuteCommandAndCaptureOutput(COMMAND_EXE, commandArgument, context); + if (string.IsNullOrEmpty(commandOut)) + { + // port is not in use + return (false, portDetail); + } + + var stringTokens = commandOut.Split(default(char[]), StringSplitOptions.RemoveEmptyEntries); + if (stringTokens.Length < MINIMUM_TOKEN_IN_A_LINE) + { + return (false, portDetail); + } + + // split host:port + var hostPortTokens = stringTokens[1].Split(new char[] { ':' }); + if (hostPortTokens.Length < 2) + { + return (false, portDetail); + } + + if (!int.TryParse(hostPortTokens[1], out var portFromHostPortToken)) + { + return (false, portDetail); + } + + if (portFromHostPortToken != port) + { + return (false, portDetail); + } + + portDetail.Port = port; + portDetail.ProcessID = int.Parse(stringTokens[4].Trim()); + (string Name, string Path) processNameAndPath; + try + { + processNameAndPath = GetProcessNameAndCommandLineArgs(portDetail.ProcessID, context); + portDetail.ProcessName = processNameAndPath.Name; + portDetail.Path = processNameAndPath.Path; + portDetail.Process = Process.GetProcessById(portDetail.ProcessID); + return (true, portDetail); + } + catch (Exception e) + { + context.API.LogException(ClassName, "Failed to get process name and path", e); + } + + return (false, portDetail); + } + + /// + /// Using WMI API to get process name and path instead of + /// Process.GetProcessById, because if calling process ins + /// 32 bit and given process id is 64 bit, caller will not be able to + /// get the process name + /// + /// + /// + /// + private static (string Name, string Path) GetProcessNameAndCommandLineArgs(int processID, PluginInitContext context) + { + var name = string.Empty; + var path = string.Empty; + string query = string.Format("Select Name,ExecutablePath from Win32_Process WHERE ProcessId='{0}'", processID); + try + { + ObjectQuery wql = new ObjectQuery(query); + ManagementObjectSearcher searcher = new ManagementObjectSearcher(wql); + ManagementObjectCollection results = searcher.Get(); + + // interested in first result. + foreach (var item in results.Cast()) + { + name = Convert.ToString(item["Name"]); + path = Convert.ToString(item["ExecutablePath"]); + break; + } + } + catch (Exception e) + { + context.API.LogException(ClassName, "Failed to get process name and path", e); + } + + return (name, path); + } + + /// + /// Execute the given command and captures the output + /// + /// + /// + /// + /// + private static string ExecuteCommandAndCaptureOutput(string commandName, string arguments, PluginInitContext context) + { + string commandOut = string.Empty; + Process process = new Process(); + process.StartInfo.FileName = commandName; + process.StartInfo.Arguments = arguments; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.Start(); + + commandOut = process.StandardOutput.ReadToEnd(); + string errors = process.StandardError.ReadToEnd(); + try + { + process.WaitForExit(TimeSpan.FromSeconds(2).Milliseconds); + } + catch (Exception exp) + { + context.API.LogException(ClassName, $"Failed to ExecuteCommandAndCaptureOutput {commandName + arguments}", exp); + } + + return commandOut; + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs index 519e8a79297..7ade32b23a6 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs @@ -36,14 +36,25 @@ internal class ProcessHelper /// /// Returns a ProcessResult for evey running non-system process whose name matches the given searchTerm /// - public List GetMatchingProcesses(string searchTerm) + public List GetMatchingProcesses(string searchTerm, PluginInitContext context) { var processlist = new List(); + bool canConvert = int.TryParse(searchTerm, out var portNum); + bool portResult = false; + PortDetail portDetail = new(); + if (canConvert) + { + (portResult, portDetail) = PortHelper.GetPortDetails(portNum, context); + } + foreach (var p in Process.GetProcesses()) { if (IsSystemProcess(p)) continue; - + if (portResult && portDetail.Process.Id == p.Id) + { + continue; + } if (string.IsNullOrWhiteSpace(searchTerm)) { // show all non-system processes @@ -59,6 +70,11 @@ public List GetMatchingProcesses(string searchTerm) } } + if (portResult) + { + var p = portDetail.Process; + processlist.Add(new ProcessResult(p, StringMatcher.FuzzySearch(searchTerm, p.ProcessName + p.Id).Score, portNum)); + } return processlist; } @@ -83,6 +99,26 @@ public void TryKill(Process p) catch (Exception e) { Log.Exception($"{nameof(ProcessHelper)}", $"Failed to kill process {p.ProcessName}", e); + TryKillRunAs(p); + } + } + + public void TryKillRunAs(Process p) + { + try + { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "powershell.exe"; + startInfo.Arguments = $"Start cmd.exe -ArgumentList \"/k\",\"taskkill\",\"/f\",\"/pid\", \"{p.Id}\" -Verb Runas"; + startInfo.UseShellExecute = false; + process.StartInfo = startInfo; + process.Start(); + } + catch(Exception e) + { + Log.Exception($"{nameof(ProcessHelper)}", $"Failed to kill process again of run as admin {p.ProcessName}", e); } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs index 03856677e63..d3de0456b56 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs @@ -10,8 +10,17 @@ public ProcessResult(Process process, int score) Score = score; } + public ProcessResult(Process process, int score, int port) + { + Process = process; + Score = score; + Port = port; + } + public Process Process { get; } public int Score { get; } + + public int Port { set; get; } = 0; } } \ No newline at end of file