2017-10-25 02:31:37 +00:00
|
|
|
using System;
|
2015-05-20 23:22:10 +00:00
|
|
|
using System.Collections;
|
2013-08-14 04:40:34 +00:00
|
|
|
using System.Collections.Generic;
|
2015-05-20 23:22:10 +00:00
|
|
|
using System.Collections.Specialized;
|
2013-08-07 05:32:22 +00:00
|
|
|
using System.ComponentModel;
|
2011-10-07 03:37:41 +00:00
|
|
|
using System.Diagnostics;
|
2013-08-07 05:32:22 +00:00
|
|
|
using System.IO;
|
2011-10-07 03:37:41 +00:00
|
|
|
using System.Linq;
|
|
|
|
using NLog;
|
2013-08-14 04:25:38 +00:00
|
|
|
using NzbDrone.Common.EnvironmentInfo;
|
2011-10-23 05:26:43 +00:00
|
|
|
using NzbDrone.Common.Model;
|
2011-10-07 03:37:41 +00:00
|
|
|
|
2013-09-20 23:56:17 +00:00
|
|
|
namespace NzbDrone.Common.Processes
|
2011-10-07 03:37:41 +00:00
|
|
|
{
|
2013-05-10 23:53:50 +00:00
|
|
|
public interface IProcessProvider
|
|
|
|
{
|
|
|
|
ProcessInfo GetCurrentProcess();
|
|
|
|
ProcessInfo GetProcessById(int id);
|
2013-11-26 02:46:12 +00:00
|
|
|
List<ProcessInfo> FindProcessByName(string name);
|
2013-08-14 05:20:24 +00:00
|
|
|
void OpenDefaultBrowser(string url);
|
2013-05-10 23:53:50 +00:00
|
|
|
void WaitForExit(Process process);
|
|
|
|
void SetPriority(int processId, ProcessPriorityClass priority);
|
|
|
|
void KillAll(string processName);
|
2013-11-26 07:08:12 +00:00
|
|
|
void Kill(int processId);
|
2015-10-03 17:45:26 +00:00
|
|
|
bool Exists(int processId);
|
|
|
|
bool Exists(string processName);
|
2013-07-30 20:19:09 +00:00
|
|
|
ProcessPriorityClass GetCurrentProcessPriority();
|
2015-05-20 23:22:10 +00:00
|
|
|
Process Start(string path, string args = null, StringDictionary environmentVariables = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null);
|
2017-10-25 02:31:37 +00:00
|
|
|
Process SpawnNewProcess(string path, string args = null, StringDictionary environmentVariables = null, bool noWindow = false);
|
2015-05-20 23:22:10 +00:00
|
|
|
ProcessOutput StartAndCapture(string path, string args = null, StringDictionary environmentVariables = null);
|
2013-05-10 23:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public class ProcessProvider : IProcessProvider
|
2011-10-07 03:37:41 +00:00
|
|
|
{
|
2014-12-17 07:12:26 +00:00
|
|
|
private readonly Logger _logger;
|
2011-10-07 03:37:41 +00:00
|
|
|
|
2017-09-27 02:06:05 +00:00
|
|
|
public const string LIDARR_PROCESS_NAME = "Lidarr";
|
|
|
|
public const string LIDARR_CONSOLE_PROCESS_NAME = "Lidarr.Console";
|
2011-10-07 03:37:41 +00:00
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
public ProcessProvider(Logger logger)
|
|
|
|
{
|
|
|
|
_logger = logger;
|
|
|
|
}
|
|
|
|
|
2021-04-26 18:07:16 +00:00
|
|
|
public static int GetCurrentProcessId()
|
2015-06-06 06:12:29 +00:00
|
|
|
{
|
2021-04-26 18:07:16 +00:00
|
|
|
return Environment.ProcessId;
|
2015-06-06 06:12:29 +00:00
|
|
|
}
|
|
|
|
|
2013-07-05 18:51:38 +00:00
|
|
|
public ProcessInfo GetCurrentProcess()
|
2011-10-07 03:37:41 +00:00
|
|
|
{
|
2011-10-07 06:36:04 +00:00
|
|
|
return ConvertToProcessInfo(Process.GetCurrentProcess());
|
|
|
|
}
|
2011-10-07 03:37:41 +00:00
|
|
|
|
2014-05-24 19:06:32 +00:00
|
|
|
public bool Exists(int processId)
|
|
|
|
{
|
|
|
|
return GetProcessById(processId) != null;
|
|
|
|
}
|
|
|
|
|
2015-10-03 17:45:26 +00:00
|
|
|
public bool Exists(string processName)
|
2013-07-30 20:19:09 +00:00
|
|
|
{
|
2013-08-14 04:40:34 +00:00
|
|
|
return GetProcessesByName(processName).Any();
|
2013-07-30 20:19:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public ProcessPriorityClass GetCurrentProcessPriority()
|
|
|
|
{
|
|
|
|
return Process.GetCurrentProcess().PriorityClass;
|
|
|
|
}
|
|
|
|
|
2013-07-05 18:51:38 +00:00
|
|
|
public ProcessInfo GetProcessById(int id)
|
2011-10-07 06:36:04 +00:00
|
|
|
{
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Debug("Finding process with Id:{0}", id);
|
2011-12-12 06:52:58 +00:00
|
|
|
|
2013-05-07 05:54:21 +00:00
|
|
|
var processInfo = ConvertToProcessInfo(Process.GetProcesses().FirstOrDefault(p => p.Id == id));
|
2011-12-12 06:52:58 +00:00
|
|
|
|
|
|
|
if (processInfo == null)
|
|
|
|
{
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Warn("Unable to find process with ID {0}", id);
|
2011-12-12 06:52:58 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Debug("Found process {0}", processInfo.ToString());
|
2011-12-12 06:52:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return processInfo;
|
2011-10-07 06:36:04 +00:00
|
|
|
}
|
2011-10-07 03:37:41 +00:00
|
|
|
|
2013-11-26 02:46:12 +00:00
|
|
|
public List<ProcessInfo> FindProcessByName(string name)
|
|
|
|
{
|
2014-04-28 06:34:29 +00:00
|
|
|
return GetProcessesByName(name).Select(ConvertToProcessInfo).Where(c => c != null).ToList();
|
2013-11-26 02:46:12 +00:00
|
|
|
}
|
|
|
|
|
2013-08-14 05:20:24 +00:00
|
|
|
public void OpenDefaultBrowser(string url)
|
2011-10-07 03:37:41 +00:00
|
|
|
{
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Info("Opening URL [{0}]", url);
|
2013-08-14 05:20:24 +00:00
|
|
|
|
|
|
|
var process = new Process
|
|
|
|
{
|
|
|
|
StartInfo = new ProcessStartInfo(url)
|
2017-01-05 23:32:17 +00:00
|
|
|
{
|
|
|
|
UseShellExecute = true
|
|
|
|
}
|
2013-08-14 05:20:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
process.Start();
|
2011-10-07 03:37:41 +00:00
|
|
|
}
|
|
|
|
|
2015-05-20 23:22:10 +00:00
|
|
|
public Process Start(string path, string args = null, StringDictionary environmentVariables = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null)
|
2013-08-07 05:32:22 +00:00
|
|
|
{
|
2019-06-29 22:33:49 +00:00
|
|
|
(path, args) = GetPathAndArgs(path, args);
|
2013-08-14 04:25:38 +00:00
|
|
|
|
2013-08-07 05:32:22 +00:00
|
|
|
var logger = LogManager.GetLogger(new FileInfo(path).Name);
|
|
|
|
|
|
|
|
var startInfo = new ProcessStartInfo(path, args)
|
|
|
|
{
|
|
|
|
CreateNoWindow = true,
|
|
|
|
UseShellExecute = false,
|
|
|
|
RedirectStandardError = true,
|
|
|
|
RedirectStandardOutput = true,
|
2013-08-14 03:22:28 +00:00
|
|
|
RedirectStandardInput = true
|
2013-08-07 05:32:22 +00:00
|
|
|
};
|
|
|
|
|
2015-05-20 23:22:10 +00:00
|
|
|
if (environmentVariables != null)
|
|
|
|
{
|
|
|
|
foreach (DictionaryEntry environmentVariable in environmentVariables)
|
|
|
|
{
|
2017-12-24 06:22:48 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
|
|
|
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
if (environmentVariable.Value == null)
|
|
|
|
{
|
|
|
|
_logger.Error(e, "Unable to set environment variable '{0}', value is null", environmentVariable.Key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_logger.Error(e, "Unable to set environment variable '{0}'", environmentVariable.Key);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw;
|
|
|
|
}
|
2015-05-20 23:22:10 +00:00
|
|
|
}
|
|
|
|
}
|
2013-08-07 05:32:22 +00:00
|
|
|
|
2014-03-27 22:13:37 +00:00
|
|
|
logger.Debug("Starting {0} {1}", path, args);
|
2013-08-07 05:32:22 +00:00
|
|
|
|
2013-08-13 05:08:37 +00:00
|
|
|
var process = new Process
|
2017-01-05 23:32:17 +00:00
|
|
|
{
|
|
|
|
StartInfo = startInfo
|
|
|
|
};
|
2013-08-07 05:32:22 +00:00
|
|
|
|
|
|
|
process.OutputDataReceived += (sender, eventArgs) =>
|
|
|
|
{
|
2020-01-03 12:49:24 +00:00
|
|
|
if (string.IsNullOrWhiteSpace(eventArgs.Data))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2013-08-07 05:32:22 +00:00
|
|
|
|
|
|
|
logger.Debug(eventArgs.Data);
|
|
|
|
|
|
|
|
if (onOutputDataReceived != null)
|
|
|
|
{
|
|
|
|
onOutputDataReceived(eventArgs.Data);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
process.ErrorDataReceived += (sender, eventArgs) =>
|
|
|
|
{
|
2020-01-03 12:49:24 +00:00
|
|
|
if (string.IsNullOrWhiteSpace(eventArgs.Data))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2013-08-07 05:32:22 +00:00
|
|
|
|
|
|
|
logger.Error(eventArgs.Data);
|
|
|
|
|
|
|
|
if (onErrorDataReceived != null)
|
|
|
|
{
|
|
|
|
onErrorDataReceived(eventArgs.Data);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-14 03:22:28 +00:00
|
|
|
process.Start();
|
|
|
|
|
2013-08-07 05:32:22 +00:00
|
|
|
process.BeginErrorReadLine();
|
|
|
|
process.BeginOutputReadLine();
|
|
|
|
|
|
|
|
return process;
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:31:37 +00:00
|
|
|
public Process SpawnNewProcess(string path, string args = null, StringDictionary environmentVariables = null, bool noWindow = false)
|
2013-08-20 22:12:35 +00:00
|
|
|
{
|
2019-06-29 22:33:49 +00:00
|
|
|
(path, args) = GetPathAndArgs(path, args);
|
2013-08-20 22:12:35 +00:00
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Debug("Starting {0} {1}", path, args);
|
2013-08-20 22:12:35 +00:00
|
|
|
|
|
|
|
var startInfo = new ProcessStartInfo(path, args);
|
2017-10-25 02:31:37 +00:00
|
|
|
startInfo.CreateNoWindow = noWindow;
|
|
|
|
startInfo.UseShellExecute = !noWindow;
|
|
|
|
|
2013-08-20 22:12:35 +00:00
|
|
|
var process = new Process
|
|
|
|
{
|
|
|
|
StartInfo = startInfo
|
|
|
|
};
|
|
|
|
|
|
|
|
process.Start();
|
|
|
|
|
|
|
|
return process;
|
|
|
|
}
|
|
|
|
|
2015-05-20 23:22:10 +00:00
|
|
|
public ProcessOutput StartAndCapture(string path, string args = null, StringDictionary environmentVariables = null)
|
2013-09-20 23:56:17 +00:00
|
|
|
{
|
|
|
|
var output = new ProcessOutput();
|
2020-01-03 12:49:24 +00:00
|
|
|
var process = Start(path,
|
|
|
|
args,
|
|
|
|
environmentVariables,
|
|
|
|
s => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Standard, s)),
|
|
|
|
error => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Error, error)));
|
2015-05-20 23:22:10 +00:00
|
|
|
|
|
|
|
process.WaitForExit();
|
|
|
|
output.ExitCode = process.ExitCode;
|
2013-09-20 23:56:17 +00:00
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2013-07-05 18:51:38 +00:00
|
|
|
public void WaitForExit(Process process)
|
2012-02-26 21:22:35 +00:00
|
|
|
{
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Debug("Waiting for process {0} to exit.", process.ProcessName);
|
2014-02-13 19:15:15 +00:00
|
|
|
|
2014-02-14 02:18:11 +00:00
|
|
|
process.WaitForExit();
|
2012-02-26 21:22:35 +00:00
|
|
|
}
|
|
|
|
|
2013-07-05 18:51:38 +00:00
|
|
|
public void SetPriority(int processId, ProcessPriorityClass priority)
|
2011-10-07 06:36:04 +00:00
|
|
|
{
|
|
|
|
var process = Process.GetProcessById(processId);
|
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Info("Updating [{0}] process priority from {1} to {2}",
|
2011-10-07 06:36:04 +00:00
|
|
|
process.ProcessName,
|
|
|
|
process.PriorityClass,
|
|
|
|
priority);
|
|
|
|
|
|
|
|
process.PriorityClass = priority;
|
|
|
|
}
|
|
|
|
|
2014-04-28 06:34:29 +00:00
|
|
|
public void Kill(int processId)
|
|
|
|
{
|
|
|
|
var process = Process.GetProcesses().FirstOrDefault(p => p.Id == processId);
|
|
|
|
|
|
|
|
if (process == null)
|
|
|
|
{
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Warn("Cannot find process with id: {0}", processId);
|
2014-04-28 06:34:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
process.Refresh();
|
|
|
|
|
2021-04-26 18:07:16 +00:00
|
|
|
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
2014-04-28 06:34:29 +00:00
|
|
|
{
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Debug("Process has already exited");
|
2014-04-28 06:34:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Info("[{0}]: Killing process", process.Id);
|
2014-04-28 06:34:29 +00:00
|
|
|
process.Kill();
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Info("[{0}]: Waiting for exit", process.Id);
|
2014-04-28 06:34:29 +00:00
|
|
|
process.WaitForExit();
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Info("[{0}]: Process terminated successfully", process.Id);
|
2014-04-28 06:34:29 +00:00
|
|
|
}
|
|
|
|
|
2013-07-27 00:56:49 +00:00
|
|
|
public void KillAll(string processName)
|
|
|
|
{
|
2014-04-28 06:34:29 +00:00
|
|
|
var processes = GetProcessesByName(processName);
|
2013-07-27 00:56:49 +00:00
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Debug("Found {0} processes to kill", processes.Count);
|
2014-04-28 06:34:29 +00:00
|
|
|
|
|
|
|
foreach (var processInfo in processes)
|
2013-07-27 00:56:49 +00:00
|
|
|
{
|
2021-04-26 18:07:16 +00:00
|
|
|
if (processInfo.Id == GetCurrentProcessId())
|
2015-07-08 18:54:47 +00:00
|
|
|
{
|
|
|
|
_logger.Debug("Tried killing own process, skipping: {0} [{1}]", processInfo.Id, processInfo.ProcessName);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Debug("Killing process: {0} [{1}]", processInfo.Id, processInfo.ProcessName);
|
2013-07-27 00:56:49 +00:00
|
|
|
Kill(processInfo.Id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
private ProcessInfo ConvertToProcessInfo(Process process)
|
2011-10-07 06:36:04 +00:00
|
|
|
{
|
2020-01-03 12:49:24 +00:00
|
|
|
if (process == null)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
2013-07-30 20:19:09 +00:00
|
|
|
|
|
|
|
process.Refresh();
|
|
|
|
|
2013-11-26 02:46:12 +00:00
|
|
|
ProcessInfo processInfo = null;
|
|
|
|
|
2013-07-30 20:19:09 +00:00
|
|
|
try
|
|
|
|
{
|
2020-01-03 12:49:24 +00:00
|
|
|
if (process.Id <= 0)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
2013-11-26 02:46:12 +00:00
|
|
|
|
|
|
|
processInfo = new ProcessInfo();
|
|
|
|
processInfo.Id = process.Id;
|
|
|
|
processInfo.Name = process.ProcessName;
|
|
|
|
processInfo.StartPath = GetExeFileName(process);
|
2013-07-30 20:19:09 +00:00
|
|
|
|
2021-04-26 18:07:16 +00:00
|
|
|
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
2013-07-30 20:19:09 +00:00
|
|
|
{
|
2013-11-26 02:46:12 +00:00
|
|
|
processInfo = null;
|
|
|
|
}
|
2013-07-30 20:19:09 +00:00
|
|
|
}
|
2013-11-26 02:46:12 +00:00
|
|
|
catch (Win32Exception e)
|
2013-07-30 20:19:09 +00:00
|
|
|
{
|
2016-02-11 21:13:42 +00:00
|
|
|
_logger.Warn(e, "Couldn't get process info for " + process.ProcessName);
|
2013-07-30 20:19:09 +00:00
|
|
|
}
|
|
|
|
|
2013-11-26 02:46:12 +00:00
|
|
|
return processInfo;
|
2011-10-07 03:37:41 +00:00
|
|
|
}
|
2013-05-04 21:09:25 +00:00
|
|
|
|
2013-09-15 00:17:45 +00:00
|
|
|
private static string GetExeFileName(Process process)
|
|
|
|
{
|
|
|
|
if (process.MainModule.FileName != "mono.exe")
|
|
|
|
{
|
|
|
|
return process.MainModule.FileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
|
|
|
|
}
|
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
private List<Process> GetProcessesByName(string name)
|
2013-07-05 18:51:38 +00:00
|
|
|
{
|
2014-04-28 06:34:29 +00:00
|
|
|
//TODO: move this to an OS specific class
|
|
|
|
var monoProcesses = Process.GetProcessesByName("mono")
|
|
|
|
.Union(Process.GetProcessesByName("mono-sgen"))
|
|
|
|
.Where(process =>
|
|
|
|
process.Modules.Cast<ProcessModule>()
|
|
|
|
.Any(module =>
|
|
|
|
module.ModuleName.ToLower() == name.ToLower() + ".exe"));
|
2013-07-05 18:51:38 +00:00
|
|
|
|
2014-04-28 06:34:29 +00:00
|
|
|
var processes = Process.GetProcessesByName(name)
|
|
|
|
.Union(monoProcesses).ToList();
|
2013-07-05 18:51:38 +00:00
|
|
|
|
2014-12-17 07:12:26 +00:00
|
|
|
_logger.Debug("Found {0} processes with the name: {1}", processes.Count, name);
|
2013-07-05 18:51:38 +00:00
|
|
|
|
2015-07-08 18:54:47 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
foreach (var process in processes)
|
|
|
|
{
|
|
|
|
_logger.Debug(" - [{0}] {1}", process.Id, process.ProcessName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
// Don't crash on gettings some log data.
|
|
|
|
}
|
|
|
|
|
2014-04-28 06:34:29 +00:00
|
|
|
return processes;
|
2013-07-05 18:51:38 +00:00
|
|
|
}
|
2015-02-06 01:15:45 +00:00
|
|
|
|
2019-06-29 22:33:49 +00:00
|
|
|
private (string Path, string Args) GetPathAndArgs(string path, string args)
|
2015-02-06 01:15:45 +00:00
|
|
|
{
|
2019-06-29 22:33:49 +00:00
|
|
|
if (OsInfo.IsWindows && path.EndsWith(".bat", StringComparison.InvariantCultureIgnoreCase))
|
|
|
|
{
|
|
|
|
return ("cmd.exe", $"/c {path} {args}");
|
|
|
|
}
|
|
|
|
|
2019-07-02 18:50:32 +00:00
|
|
|
if (OsInfo.IsWindows && path.EndsWith(".ps1", StringComparison.InvariantCultureIgnoreCase))
|
|
|
|
{
|
|
|
|
return ("powershell.exe", $"-ExecutionPolicy Bypass -NoProfile -File {path} {args}");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (OsInfo.IsWindows && path.EndsWith(".py", StringComparison.InvariantCultureIgnoreCase))
|
|
|
|
{
|
|
|
|
return ("python.exe", $"{path} {args}");
|
|
|
|
}
|
|
|
|
|
2019-06-29 22:33:49 +00:00
|
|
|
return (path, args);
|
2015-02-06 01:15:45 +00:00
|
|
|
}
|
2011-10-07 03:37:41 +00:00
|
|
|
}
|
2013-08-13 14:23:47 +00:00
|
|
|
}
|