This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using MonsterMonitor.Models;
|
||||
|
||||
namespace MonsterMonitor.Services
|
||||
{
|
||||
public sealed class AppController : IDisposable
|
||||
{
|
||||
private readonly LogService _log;
|
||||
private readonly PowerManagementService _power;
|
||||
private readonly SsConfigService _ssConfig;
|
||||
private ProcessMonitorService _processMonitor;
|
||||
private SshTunnelService _sshTunnel;
|
||||
|
||||
public AppController(LogService log, PowerManagementService power)
|
||||
{
|
||||
_log = log;
|
||||
_power = power;
|
||||
_ssConfig = new SsConfigService(_log);
|
||||
}
|
||||
|
||||
public void Start(AppSettings settings)
|
||||
{
|
||||
Stop();
|
||||
|
||||
_power.PreventSleep();
|
||||
_processMonitor = new ProcessMonitorService(_log);
|
||||
_sshTunnel = new SshTunnelService(_log);
|
||||
|
||||
_ssConfig.EnsureConfig(settings);
|
||||
_processMonitor.Start(settings.SsProcessPath, settings.SsArguments);
|
||||
_sshTunnel.Start(settings);
|
||||
|
||||
_log.Info("Сервисы приложения запущены.");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_sshTunnel?.Dispose();
|
||||
_sshTunnel = null;
|
||||
|
||||
_processMonitor?.Dispose();
|
||||
_processMonitor = null;
|
||||
|
||||
_power.RestoreDefault();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using MonsterMonitor.Models;
|
||||
|
||||
namespace MonsterMonitor.Services
|
||||
{
|
||||
public class AuthMonitor: IAuthMonitor
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
||||
|
||||
private readonly AppSettings _settings;
|
||||
|
||||
public AuthMonitor(AppSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
private string GetActiveWindowTitle()
|
||||
{
|
||||
const int nChars = 256;
|
||||
var buff = new StringBuilder(nChars);
|
||||
var handle = GetForegroundWindow();
|
||||
return GetWindowText(handle, buff, nChars) > 0 ? buff.ToString() : null;
|
||||
}
|
||||
|
||||
public async Task StartMonitor()
|
||||
{
|
||||
_ = Task.Run(async () => await CheckProcess());
|
||||
}
|
||||
|
||||
private async Task CheckProcess()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process[] processlist = Process.GetProcesses();
|
||||
foreach (Process process in processlist)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(process.MainWindowTitle) && process.MainWindowTitle.Contains("XAuth request"))
|
||||
{
|
||||
SetForegroundWindow(process.MainWindowHandle);
|
||||
}
|
||||
}
|
||||
|
||||
var activeWindowTitle = GetActiveWindowTitle();
|
||||
if (activeWindowTitle?.Contains("XAuth request") == true)
|
||||
{
|
||||
var password = _settings.GetSystemPassword();
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
SendKeys.SendWait(password);
|
||||
SendKeys.SendWait("{ENTER}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message + ex.StackTrace);
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAuthMonitor
|
||||
{
|
||||
Task StartMonitor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace MonsterMonitor.Services
|
||||
{
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error
|
||||
}
|
||||
|
||||
public sealed class LogEntry
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public LogLevel Level { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public sealed class LogService
|
||||
{
|
||||
public event Action<LogEntry> LogReceived;
|
||||
|
||||
public void Log(LogLevel level, string message)
|
||||
{
|
||||
LogReceived?.Invoke(new LogEntry
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Level = level,
|
||||
Message = message ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
public void Info(string message) => Log(LogLevel.Info, message);
|
||||
public void Warn(string message) => Log(LogLevel.Warn, message);
|
||||
public void Error(string message) => Log(LogLevel.Error, message);
|
||||
public void Debug(string message) => Log(LogLevel.Debug, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MonsterMonitor.Services
|
||||
{
|
||||
public sealed class PowerManagementService
|
||||
{
|
||||
[Flags]
|
||||
private enum ExecutionState : uint
|
||||
{
|
||||
ES_SYSTEM_REQUIRED = 0x00000001,
|
||||
ES_DISPLAY_REQUIRED = 0x00000002,
|
||||
ES_CONTINUOUS = 0x80000000
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern ExecutionState SetThreadExecutionState(ExecutionState flags);
|
||||
|
||||
public void PreventSleep()
|
||||
{
|
||||
SetThreadExecutionState(
|
||||
ExecutionState.ES_CONTINUOUS |
|
||||
ExecutionState.ES_SYSTEM_REQUIRED |
|
||||
ExecutionState.ES_DISPLAY_REQUIRED);
|
||||
}
|
||||
|
||||
public void RestoreDefault()
|
||||
{
|
||||
SetThreadExecutionState(ExecutionState.ES_CONTINUOUS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace MonsterMonitor.Services
|
||||
{
|
||||
public sealed class ProcessMonitorService : IDisposable
|
||||
{
|
||||
private readonly LogService _log;
|
||||
private readonly object _sync = new object();
|
||||
private readonly Timer _watchdogTimer;
|
||||
private Process _process;
|
||||
private string _path;
|
||||
private string _arguments;
|
||||
private bool _stopping;
|
||||
|
||||
public ProcessMonitorService(LogService log)
|
||||
{
|
||||
_log = log;
|
||||
_watchdogTimer = new Timer(WatchdogTick, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
public void Start(string path, string arguments)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
_path = path;
|
||||
_arguments = arguments ?? string.Empty;
|
||||
_stopping = false;
|
||||
EnsureStarted();
|
||||
_watchdogTimer.Change(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureStarted()
|
||||
{
|
||||
if (IsProcessRunning(_process))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_process != null)
|
||||
{
|
||||
_process.Exited -= ProcessOnExited;
|
||||
_process.Dispose();
|
||||
}
|
||||
_process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = _path,
|
||||
Arguments = _arguments,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
//_process.Exited += ProcessOnExited;
|
||||
_process.Start();
|
||||
_log.Info("Процесс ss запущен.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error("Ошибка запуска ss: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessOnExited(object sender, EventArgs e)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_stopping)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_log.Warn("Процесс ss завершился, запускаю заново.");
|
||||
EnsureStarted();
|
||||
}
|
||||
}
|
||||
|
||||
private void WatchdogTick(object state)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_stopping)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureStarted();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
_stopping = true;
|
||||
_watchdogTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
|
||||
|
||||
try
|
||||
{
|
||||
if (IsProcessRunning(_process))
|
||||
{
|
||||
_process.Kill();
|
||||
_process.WaitForExit(5000);
|
||||
_log.Info("Процесс ss остановлен.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Не удалось корректно завершить ss: " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_process != null)
|
||||
{
|
||||
_process.Exited -= ProcessOnExited;
|
||||
_process.Dispose();
|
||||
}
|
||||
_process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsProcessRunning(Process process)
|
||||
{
|
||||
if (process == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return !process.HasExited;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
_watchdogTimer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using MonsterMonitor.Models;
|
||||
|
||||
namespace MonsterMonitor.Services
|
||||
{
|
||||
public sealed class SsConfigService
|
||||
{
|
||||
private readonly LogService _log;
|
||||
|
||||
public SsConfigService(LogService log)
|
||||
{
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public void EnsureConfig(AppSettings settings)
|
||||
{
|
||||
var password = settings.GetThreeProxyPassword();
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
password = GeneratePassword();
|
||||
settings.SetThreeProxyPassword(password);
|
||||
settings.Save();
|
||||
_log.Info("Сгенерирован пароль прокси и сохранен в настройках.");
|
||||
}
|
||||
var port = settings.LocalPort;
|
||||
|
||||
var config = "users admin:CL:" + password + "\r\n" +
|
||||
"auth strong\r\n" +
|
||||
$"socks -i127.0.0.1 -p{port}";
|
||||
|
||||
var configPath = GetConfigPath();
|
||||
var directory = Path.GetDirectoryName(configPath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.WriteAllText(configPath, config);
|
||||
_log.Info("Сконфигурирован ss.cfg: " + configPath);
|
||||
}
|
||||
|
||||
private static string GetConfigPath()
|
||||
{
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "ss", "ss.cfg");
|
||||
}
|
||||
|
||||
private static string GeneratePassword()
|
||||
{
|
||||
const string alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
const int length = 20;
|
||||
var bytes = new byte[length];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(bytes);
|
||||
}
|
||||
|
||||
var chars = new char[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
chars[i] = alphabet[bytes[i] % alphabet.Length];
|
||||
}
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MonsterMonitor.Models;
|
||||
using Renci.SshNet;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace MonsterMonitor.Services
|
||||
{
|
||||
public sealed class SshTunnelService : IDisposable
|
||||
{
|
||||
private readonly LogService _log;
|
||||
private readonly object _sync = new object();
|
||||
private SshClient _client;
|
||||
private ForwardedPortRemote _forwardedPort;
|
||||
private SshCommand _heartbeatCommand;
|
||||
private CancellationTokenSource _heartbeatReadTokenSource;
|
||||
private Task _heartbeatReadTask;
|
||||
private CancellationTokenSource _monitorTokenSource;
|
||||
private AppSettings _settings;
|
||||
private bool _disposed;
|
||||
private bool _reconnectInProgress;
|
||||
private DateTime _lastHeartbeatUtc = DateTime.MinValue;
|
||||
private bool _remoteIsWindows;
|
||||
|
||||
public SshTunnelService(LogService log)
|
||||
{
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public void Start(AppSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
Connect();
|
||||
StartMonitor();
|
||||
}
|
||||
|
||||
private void Connect()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
DisconnectCore();
|
||||
|
||||
var password = _settings.GetPassword();
|
||||
var authMethod = new PasswordAuthenticationMethod(_settings.SshUsername, password);
|
||||
var connectionInfo = new ConnectionInfo(_settings.SshHost, _settings.SshPort, _settings.SshUsername, authMethod);
|
||||
|
||||
_client = new SshClient(connectionInfo);
|
||||
_client.ErrorOccurred += ClientOnErrorOccurred;
|
||||
_client.Connect();
|
||||
|
||||
_forwardedPort = new ForwardedPortRemote((uint)_settings.RemotePort, "127.0.0.1", (uint)_settings.LocalPort);
|
||||
_forwardedPort.Exception += ForwardedPortOnException;
|
||||
_client.AddForwardedPort(_forwardedPort);
|
||||
_forwardedPort.Start();
|
||||
StartRemoteHeartbeatNoLock();
|
||||
|
||||
_log.Info(
|
||||
$"SSH подключен. Туннель remote:{_settings.RemotePort} -> local:{_settings.LocalPort}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ClientOnErrorOccurred(object sender, ExceptionEventArgs e)
|
||||
{
|
||||
_log.Warn("Ошибка SSH: " + e.Exception.Message);
|
||||
ReconnectSoon();
|
||||
}
|
||||
|
||||
private void ForwardedPortOnException(object sender, ExceptionEventArgs e)
|
||||
{
|
||||
_log.Warn("Ошибка перенаправления порта: " + e.Exception.Message);
|
||||
ReconnectSoon();
|
||||
}
|
||||
|
||||
private void StartMonitor()
|
||||
{
|
||||
_monitorTokenSource = new CancellationTokenSource();
|
||||
Task.Run(() => MonitorLoop(_monitorTokenSource.Token));
|
||||
}
|
||||
|
||||
private async Task MonitorLoop(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var silenceThresholdSec = Math.Max(
|
||||
5,
|
||||
_settings.MaxPingFailures);
|
||||
|
||||
var lastHeartbeat = _lastHeartbeatUtc;
|
||||
var isHeartbeatAlive = lastHeartbeat != DateTime.MinValue &&
|
||||
(DateTime.UtcNow - lastHeartbeat).TotalSeconds <= silenceThresholdSec;
|
||||
|
||||
if (!IsConnected() || !isHeartbeatAlive)
|
||||
{
|
||||
_log.Warn("Нет живого вывода heartbeat-команды на удаленном сервере. Переподключаю SSH.");
|
||||
await Reconnect().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Исключение мониторинга heartbeat: " + ex.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(2), token).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConnected()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _client != null && _client.IsConnected;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReconnectSoon()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Reconnect().ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task Reconnect()
|
||||
{
|
||||
if (_reconnectInProgress || _disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_reconnectInProgress = true;
|
||||
try
|
||||
{
|
||||
var timeout = Math.Min(60, Math.Max(5, _settings.ReconnectTimeoutSec));
|
||||
_log.Warn("Перезапуск SSH-соединения...");
|
||||
|
||||
var reconnectTask = Task.Run(() =>
|
||||
{
|
||||
DisconnectCore();
|
||||
Connect();
|
||||
});
|
||||
|
||||
var completed = await Task.WhenAny(reconnectTask, Task.Delay(TimeSpan.FromSeconds(timeout))).ConfigureAwait(false);
|
||||
if (completed != reconnectTask)
|
||||
{
|
||||
_log.Error("Переподключение превысило таймаут " + timeout + "с.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info("SSH-соединение восстановлено.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error("Ошибка переподключения SSH: " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_reconnectInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void DisconnectCore()
|
||||
{
|
||||
StopRemoteHeartbeatNoLock();
|
||||
|
||||
try
|
||||
{
|
||||
if (_forwardedPort != null)
|
||||
{
|
||||
if (_forwardedPort.IsStarted)
|
||||
{
|
||||
_forwardedPort.Stop();
|
||||
}
|
||||
_forwardedPort.Dispose();
|
||||
_forwardedPort = null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors on shutdown.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_client != null)
|
||||
{
|
||||
if (_client.IsConnected)
|
||||
{
|
||||
_client.Disconnect();
|
||||
}
|
||||
_client.Dispose();
|
||||
_client = null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors on shutdown.
|
||||
}
|
||||
}
|
||||
|
||||
private void StartRemoteHeartbeatNoLock()
|
||||
{
|
||||
if (_client == null || !_client.IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopRemoteHeartbeatNoLock();
|
||||
_remoteIsWindows = DetectRemoteWindowsNoLock();
|
||||
|
||||
var heartbeatCommand = _remoteIsWindows
|
||||
? "ping ya.ru -t"
|
||||
: "while :; do echo 'some type of ping response '$C; C=$((C+1)); sleep 1; done";
|
||||
|
||||
_heartbeatCommand = _client.CreateCommand(heartbeatCommand);
|
||||
_heartbeatReadTokenSource = new CancellationTokenSource();
|
||||
_lastHeartbeatUtc = DateTime.UtcNow;
|
||||
_heartbeatCommand.BeginExecute();
|
||||
|
||||
_heartbeatReadTask = Task.Run(() =>
|
||||
ReadHeartbeatOutputLoop(_heartbeatCommand, _heartbeatReadTokenSource.Token));
|
||||
_log.Info("Запущен удаленный heartbeat через SSH (" + (_remoteIsWindows ? "Windows" : "Linux/Unix") + ").");
|
||||
}
|
||||
|
||||
private void StopRemoteHeartbeatNoLock()
|
||||
{
|
||||
try
|
||||
{
|
||||
_heartbeatReadTokenSource?.Cancel();
|
||||
_heartbeatReadTask?.Wait(1000);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors on shutdown.
|
||||
}
|
||||
finally
|
||||
{
|
||||
_heartbeatReadTokenSource?.Dispose();
|
||||
_heartbeatReadTokenSource = null;
|
||||
_heartbeatReadTask = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_heartbeatCommand?.Dispose();
|
||||
_heartbeatCommand = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors on shutdown.
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadHeartbeatOutputLoop(SshCommand command, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var reader = new StreamReader(command.OutputStream))
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
Thread.Sleep(200);
|
||||
continue;
|
||||
}
|
||||
|
||||
_lastHeartbeatUtc = DateTime.UtcNow;
|
||||
_log.Debug("HB: " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
_log.Warn("Ошибка чтения heartbeat-вывода: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool DetectRemoteWindowsNoLock()
|
||||
{
|
||||
try
|
||||
{
|
||||
var command = _client.CreateCommand("cmd /c ver");
|
||||
var output = command.Execute() ?? string.Empty;
|
||||
return output.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_monitorTokenSource?.Cancel();
|
||||
_monitorTokenSource?.Dispose();
|
||||
_monitorTokenSource = null;
|
||||
lock (_sync)
|
||||
{
|
||||
DisconnectCore();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user