-
-
Notifications
You must be signed in to change notification settings - Fork 366
Process TPC Support & Kill Retry as Admin #1320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
f984223
4590418
650875f
0ccb2da
4bafa46
b8a7503
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}"; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// 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); | ||
/// } | ||
/// </summary> | ||
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); | ||
} | ||
|
||
/// <summary> | ||
/// 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 | ||
/// </summary> | ||
/// <param name="processID"></param> | ||
/// <param name="context"></param> | ||
/// <returns></returns> | ||
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<ManagementObject>()) | ||
{ | ||
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); | ||
} | ||
|
||
/// <summary> | ||
/// Execute the given command and captures the output | ||
/// </summary> | ||
/// <param name="commandName"></param> | ||
/// <param name="arguments"></param> | ||
/// <param name="context"></param> | ||
/// <returns></returns> | ||
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be stuck if the process never ends. I think we should put it lower after waiting process ends. Also, probably make this part async. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First of all, thank you very much for reviewing and pointing out the problems 😊, but in addition to some simple style problems, there are many problems that I can't fix due to my limited ability. You should close this pr, if you have time, can you add these two features🥺?sorry for wasting your precious time. (Actually, I know this code is shit, but I don't have the energy to learn how to write better implementations recently. 😢 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the code is that bad. Those things I mention can be adjusted fairly easily. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you very much, then I close this pr? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just leave it here. I will finish it once I got time. |
||
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; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,14 +36,25 @@ internal class ProcessHelper | |
/// <summary> | ||
/// Returns a ProcessResult for evey running non-system process whose name matches the given searchTerm | ||
/// </summary> | ||
public List<ProcessResult> GetMatchingProcesses(string searchTerm) | ||
public List<ProcessResult> GetMatchingProcesses(string searchTerm, PluginInitContext context) | ||
{ | ||
var processlist = new List<ProcessResult>(); | ||
|
||
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<ProcessResult> 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"; | ||
Comment on lines
+113
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forget this one. It's pretty weird that you start There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The startInfo do provide an |
||
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); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible to have multiple port in one line? Or that won't happen for Listening port?