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 1 commit
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 @@ -49,6 +49,10 @@
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Management" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
Expand Down
4 changes: 2 additions & 2 deletions Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ private List<Result> CreateResultsFromProcesses(List<ProcessResult> processlist,
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,
ContextData = p.ProcessName,
AutoCompleteText = $"{_context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}",
Action = (c) =>
{
{
processHelper.TryKill(p);
return true;
}
Expand Down
179 changes: 179 additions & 0 deletions Plugins/Flow.Launcher.Plugin.ProcessKiller/PortHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using System;
using System.Collections.Generic;
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 string.Format(@" Process Name: {0} ,Process ID: {1} ,
Port: {2} ,\nPath : {3}", ProcessName,
ProcessID, Port, 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";

public PortHelper()
{

}

public static Tuple<bool, PortDetail> GetPortDetails(int port)
{
PortDetail PortDetail = new PortDetail();
Tuple<bool, PortDetail> result = Tuple.Create(false, 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);
Copy link
Member

Choose a reason for hiding this comment

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

I have a feeling that netstat is a exe that can be called directly without cmd. Could you take a try?
Sorry I didn't see the findstr before. Though, I still think we shall be able to process the string directly with either regex or a manually written parser.

if (string.IsNullOrEmpty(commandOut))
{
// port is not in use
return result;
}

var stringTokens = commandOut.Split(default(Char[]), StringSplitOptions.RemoveEmptyEntries);
if (stringTokens.Length < MINIMUM_TOKEN_IN_A_LINE)
{
return result;
}

// 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 result;
}

int portFromHostPortToken = 0;
if (!int.TryParse(hostPortTokens[1], out portFromHostPortToken))
{
return result;
}
if (portFromHostPortToken != port)
{
return result;
}

PortDetail.Port = port;
PortDetail.ProcessID = int.Parse(stringTokens[4].Trim());
Tuple<string, string> processNameAndPath = null;
try
{
processNameAndPath = GetProcessNameAndCommandLineArgs(PortDetail.ProcessID);
PortDetail.ProcessName = processNameAndPath.Item1;
PortDetail.Path = processNameAndPath.Item2;
PortDetail.Process = Process.GetProcessById(PortDetail.ProcessID);
result = Tuple.Create(true, PortDetail);
}
catch (Exception exp)
{
Console.WriteLine(exp.ToString());

}

return result;

}
/// <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>
/// <returns></returns>
private static Tuple<string, string> GetProcessNameAndCommandLineArgs(int processID)
{
Tuple<string, string> result = Tuple.Create(string.Empty, 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 (ManagementObject item in results)
{
result = Tuple.Create<string, string>(Convert.ToString(item["Name"]),
Convert.ToString(item["ExecutablePath"]));
break;

}
}
catch (Exception)
{

throw;
}

return result;

}

/// <summary>
/// Execute the given command and captures the output
/// </summary>
/// <param name="commandName"></param>
/// <param name="arguments"></param>
/// <returns></returns>
private static string ExecuteCommandAndCaptureOutput(string commandName, string arguments)
{
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)
{
Log.Exception($"|ProcessKiller.CreateResultsFromProcesses|Failed to ExecuteCommandAndCaptureOutput {commandName + arguments}", exp);
Console.WriteLine(exp.ToString());
}
return commandOut;
}
}
}
37 changes: 35 additions & 2 deletions Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,19 @@ public List<ProcessResult> GetMatchingProcesses(string searchTerm)
{
var processlist = new List<ProcessResult>();

int portNum;
bool canConvert = int.TryParse(searchTerm, out portNum);
Tuple<bool, PortDetail> tcpPortListeningProcess = null;
if(canConvert)
tcpPortListeningProcess = PortHelper.GetPortDetails(portNum);

foreach (var p in Process.GetProcesses())
{
if (IsSystemProcess(p)) continue;

if (tcpPortListeningProcess != null && tcpPortListeningProcess.Item1 && tcpPortListeningProcess.Item2.Process.Id == p.Id)
{
continue;
}
if (string.IsNullOrWhiteSpace(searchTerm))
{
// show all non-system processes
Expand All @@ -56,7 +65,11 @@ public List<ProcessResult> GetMatchingProcesses(string searchTerm)
}
}
}

if (tcpPortListeningProcess != null && tcpPortListeningProcess.Item1)
{
var p = tcpPortListeningProcess.Item2.Process;
processlist.Add(new ProcessResult(p, StringMatcher.FuzzySearch(searchTerm, p.ProcessName + p.Id).Score, portNum));
}
return processlist;
}

Expand All @@ -80,6 +93,26 @@ public void TryKill(Process p)
catch (Exception e)
{
Log.Exception($"|ProcessKiller.CreateResultsFromProcesses|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($"|ProcessKiller.CreateResultsFromProcesses|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;
}
}