Skip to content

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

Draft
wants to merge 6 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Management" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Flow.Launcher.Infrastructure;

Expand Down Expand Up @@ -63,7 +63,7 @@ public List<Result> LoadContextMenus(Result result)
private List<Result> CreateResultsFromQuery(Query query)
{
string termToSearch = query.Search;
var processlist = processHelper.GetMatchingProcesses(termToSearch);
var processlist = processHelper.GetMatchingProcesses(termToSearch, _context);

if (!processlist.Any())
{
Expand All @@ -79,7 +79,7 @@ private List<Result> 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,
Expand Down
168 changes: 168 additions & 0 deletions Plugins/Flow.Launcher.Plugin.ProcessKiller/PortHelper.cs
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[] { ':' });
Copy link
Member

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?

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();
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Author

@watchingfun watchingfun Aug 10, 2022

Choose a reason for hiding this comment

The 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. 😢

Copy link
Member

@taooceros taooceros Aug 10, 2022

Choose a reason for hiding this comment

The 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.
Though, if you don't have time, I can work on this based on your work for sure (but I probably will not be free to do that for a while).
Thanks for the enhancement idea and an implementation draft!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much, then I close this pr?

Copy link
Member

Choose a reason for hiding this comment

The 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;
}
}
}
40 changes: 38 additions & 2 deletions Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}

Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forget this one. It's pretty weird that you start taskkill inside a cmd inside the powershell.

Copy link
Member

@taooceros taooceros Aug 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The startInfo do provide an ArgumentList and Verb API that worth taking a look.

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);
}
}

Expand Down
9 changes: 9 additions & 0 deletions Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading