Extract interfaces from all agent services. Replace use of static Logger.

This commit is contained in:
Jared Goodwin 2023-06-24 12:05:11 -07:00
parent 402d5b020f
commit 7459f2f2dd
14 changed files with 183 additions and 107 deletions

View File

@ -94,12 +94,12 @@ public class Program
services.AddSingleton<ICpuUtilizationSampler, CpuUtilizationSampler>();
services.AddSingleton<IWakeOnLanService, WakeOnLanService>();
services.AddHostedService(services => services.GetRequiredService<ICpuUtilizationSampler>());
services.AddScoped<ChatClientService>();
services.AddTransient<PSCore>();
services.AddTransient<ExternalScriptingShell>();
services.AddScoped<ConfigService>();
services.AddScoped<Uninstaller>();
services.AddScoped<ScriptExecutor>();
services.AddScoped<IChatClientService, ChatClientService>();
services.AddTransient<IPSCore, PSCore>();
services.AddTransient<IExternalScriptingShell, ExternalScriptingShell>();
services.AddScoped<IConfigService, ConfigService>();
services.AddScoped<IUninstaller, Uninstaller>();
services.AddScoped<IScriptExecutor, ScriptExecutor>();
services.AddScoped<IProcessInvoker, ProcessInvoker>();
services.AddScoped<IUpdateDownloader, UpdateDownloader>();

View File

@ -29,20 +29,15 @@ namespace Remotely.Agent.Services
public class AgentHubConnection : IAgentHubConnection, IDisposable
{
private readonly IAppLauncher _appLauncher;
private readonly ChatClientService _chatService;
private readonly ConfigService _configService;
private readonly IChatClientService _chatService;
private readonly IConfigService _configService;
private readonly IDeviceInformationService _deviceInfoService;
private readonly IHttpClientFactory _httpFactory;
private readonly IWakeOnLanService _wakeOnLanService;
private readonly ILogger<AgentHubConnection> _logger;
private readonly ILogger _fileLogger;
private readonly ScriptExecutor _scriptExecutor;
private readonly Uninstaller _uninstaller;
private readonly IScriptExecutor _scriptExecutor;
private readonly IUninstaller _uninstaller;
private readonly IUpdater _updater;
private ConnectionInfo _connectionInfo;
@ -50,10 +45,11 @@ namespace Remotely.Agent.Services
private Timer _heartbeatTimer;
private bool _isServerVerified;
public AgentHubConnection(ConfigService configService,
Uninstaller uninstaller,
ScriptExecutor scriptExecutor,
ChatClientService chatService,
public AgentHubConnection(
IConfigService configService,
IUninstaller uninstaller,
IScriptExecutor scriptExecutor,
IChatClientService chatService,
IAppLauncher appLauncher,
IUpdater updater,
IDeviceInformationService deviceInfoService,

View File

@ -1,5 +1,6 @@
using Immense.RemoteControl.Shared.Models;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using Remotely.Agent.Interfaces;
using Remotely.Agent.Models;
using Remotely.Shared.Models;
@ -15,18 +16,19 @@ using System.Threading.Tasks;
namespace Remotely.Agent.Services
{
public class ChatClientService
public interface IChatClientService
{
Task SendMessage(string senderName, string message, string orgName, string orgId, bool disconnected, string senderConnectionID, HubConnection hubConnection);
}
public class ChatClientService : IChatClientService
{
private readonly IAppLauncher _appLauncher;
private readonly ILogger<ChatClientService> _logger;
private readonly MemoryCache _chatClients = new("ChatClients");
private readonly SemaphoreSlim _messageLock = new(1,1);
private readonly SemaphoreSlim _messageLock = new(1, 1);
public ChatClientService(IAppLauncher appLauncher)
{
_appLauncher = appLauncher;
}
private CacheItemPolicy CacheItemPolicy { get; } = new()
private readonly CacheItemPolicy _cacheItemPolicy = new()
{
SlidingExpiration = TimeSpan.FromMinutes(10),
RemovedCallback = new CacheEntryRemovedCallback(args =>
@ -48,6 +50,13 @@ namespace Remotely.Agent.Services
})
};
public ChatClientService(
IAppLauncher appLauncher,
ILogger<ChatClientService> logger)
{
_appLauncher = appLauncher;
_logger = logger;
}
public async Task SendMessage(
string senderName,
@ -60,7 +69,7 @@ namespace Remotely.Agent.Services
{
if (!await _messageLock.WaitAsync(30000))
{
Logger.Write("Timed out waiting for chat message lock.", Shared.Enums.EventType.Warning);
_logger.LogWarning("Timed out waiting for chat message lock.");
return;
}
@ -80,11 +89,11 @@ namespace Remotely.Agent.Services
if (procID > 0)
{
Logger.Write($"Chat app started. Process ID: {procID}");
_logger.LogInformation("Chat app started. Process ID: {procID}", procID);
}
else
{
Logger.Write($"Chat app did not start successfully.");
_logger.LogError($"Chat app did not start successfully.");
return;
}
@ -92,12 +101,12 @@ namespace Remotely.Agent.Services
clientPipe.Connect(15000);
if (!clientPipe.IsConnected)
{
Logger.Write("Failed to connect to chat host.");
_logger.LogError("Failed to connect to chat host.");
return;
}
chatSession = new ChatSession() { PipeStream = clientPipe, ProcessID = procID };
_ = Task.Run(async () => { await ReadFromStream(chatSession.PipeStream, senderConnectionID, hubConnection); });
_chatClients.Add(senderConnectionID, chatSession, CacheItemPolicy);
_chatClients.Add(senderConnectionID, chatSession, _cacheItemPolicy);
}
chatSession = (ChatSession)_chatClients.Get(senderConnectionID);
@ -116,7 +125,7 @@ namespace Remotely.Agent.Services
}
catch (Exception ex)
{
Logger.Write(ex);
_logger.LogError(ex, "Error while sending chat message.");
}
finally
{

View File

@ -1,4 +1,5 @@
using Remotely.Shared.Models;
using Microsoft.Extensions.Logging;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
@ -8,11 +9,23 @@ using System.Text.Json;
namespace Remotely.Agent.Services
{
public class ConfigService
public interface IConfigService
{
ConnectionInfo GetConnectionInfo();
void SaveConnectionInfo(ConnectionInfo connectionInfo);
}
public class ConfigService : IConfigService
{
private static readonly object _fileLock = new();
private ConnectionInfo _connectionInfo;
private readonly string _debugGuid = "f2b0a595-5ea8-471b-975f-12e70e0f3497";
private readonly ILogger<ConfigService> _logger;
public ConfigService(ILogger<ConfigService> logger)
{
_logger = logger;
}
private Dictionary<string, string> _commandLineArgs;
private Dictionary<string, string> CommandLineArgs
@ -74,7 +87,7 @@ namespace Remotely.Agent.Services
{
if (!File.Exists("ConnectionInfo.json"))
{
Logger.Write(new Exception("No connection info available. Please create ConnectionInfo.json file with appropriate values."));
_logger.LogError("No connection info available. Please create ConnectionInfo.json file with appropriate values.");
return null;
}
_connectionInfo = JsonSerializer.Deserialize<ConnectionInfo>(File.ReadAllText("ConnectionInfo.json"));

View File

@ -13,7 +13,7 @@ using System.Runtime.InteropServices;
namespace Remotely.Agent.Services
{
public class DeviceInfoGeneratorBase
public abstract class DeviceInfoGeneratorBase
{
protected readonly ILogger<DeviceInfoGeneratorBase> _logger;

View File

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.Shared.Enums;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
@ -15,19 +16,23 @@ namespace Remotely.Agent.Services
{
public interface IExternalScriptingShell
{
ScriptResult WriteInput(string input, TimeSpan timeout);
}
public class ExternalScriptingShell : IExternalScriptingShell
{
private static readonly ConcurrentDictionary<string, ExternalScriptingShell> _sessions = new();
private readonly ConfigService _configService;
private readonly IConfigService _configService;
private readonly ILogger<ExternalScriptingShell> _logger;
private string _lineEnding;
private ScriptingShell _shell;
public ExternalScriptingShell(ConfigService configService)
public ExternalScriptingShell(
IConfigService configService,
ILogger<ExternalScriptingShell> logger)
{
_configService = configService;
_logger = logger;
}
private string ErrorOut { get; set; }
@ -46,6 +51,7 @@ namespace Remotely.Agent.Services
private Stopwatch Stopwatch { get; set; }
// TODO: Turn into cache and factory.
public static ExternalScriptingShell GetCurrent(ScriptingShell shell, string senderConnectionId)
{
if (_sessions.TryGetValue($"{shell}-{senderConnectionId}", out var session) &&
@ -112,7 +118,7 @@ namespace Remotely.Agent.Services
}
catch (Exception ex)
{
Logger.Write(ex);
_logger.LogError(ex, "Error while writing input to scripting shell.");
ErrorOut += Environment.NewLine + ex.Message;
// Something's wrong. Let the next command start a new session.

View File

@ -24,7 +24,7 @@ namespace Remotely.Agent.Services.Linux
private readonly ILogger<AppLauncherLinux> _logger;
public AppLauncherLinux(
ConfigService configService,
IConfigService configService,
IProcessInvoker processInvoker,
ILogger<AppLauncherLinux> logger)
{

View File

@ -20,7 +20,7 @@ namespace Remotely.Agent.Services.Linux
public class UpdaterLinux : IUpdater
{
private readonly SemaphoreSlim _checkForUpdatesLock = new(1, 1);
private readonly ConfigService _configService;
private readonly IConfigService _configService;
private readonly IUpdateDownloader _updateDownloader;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<UpdaterLinux> _logger;
@ -29,7 +29,7 @@ namespace Remotely.Agent.Services.Linux
private DateTimeOffset _lastUpdateFailure;
public UpdaterLinux(
ConfigService configService,
IConfigService configService,
IUpdateDownloader updateDownloader,
IHttpClientFactory httpClientFactory,
ILogger<UpdaterLinux> logger)

View File

@ -21,7 +21,7 @@ namespace Remotely.Agent.Services.MacOS
{
private readonly string _achitecture = RuntimeInformation.OSArchitecture.ToString().ToLower();
private readonly SemaphoreSlim _checkForUpdatesLock = new(1, 1);
private readonly ConfigService _configService;
private readonly IConfigService _configService;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IUpdateDownloader _updateDownloader;
private readonly ILogger<UpdaterMac> _logger;
@ -30,7 +30,7 @@ namespace Remotely.Agent.Services.MacOS
private readonly System.Timers.Timer _updateTimer = new(TimeSpan.FromHours(6).TotalMilliseconds);
public UpdaterMac(
ConfigService configService,
IConfigService configService,
IUpdateDownloader updateDownloader,
IHttpClientFactory httpClientFactory,
ILogger<UpdaterMac> logger)

View File

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.Shared.Models;
using System;
using System.Collections.Concurrent;
@ -7,22 +8,35 @@ using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Timers;
using static Immense.RemoteControl.Desktop.Native.Windows.User32;
namespace Remotely.Agent.Services
{
public class PSCore
public interface IPSCore
{
private readonly ConfigService _configService;
string SenderConnectionId { get; }
CommandCompletion GetCompletions(string inputText, int currentIndex, bool? forward);
ScriptResult WriteInput(string input);
}
public class PSCore : IPSCore
{
private static readonly ConcurrentDictionary<string, PSCore> _sessions = new ConcurrentDictionary<string, PSCore>();
private readonly IConfigService _configService;
private readonly ConnectionInfo _connectionInfo;
private readonly ILogger<PSCore> _logger;
private readonly PowerShell _powershell;
private CommandCompletion _lastCompletion;
private string _lastInputText;
private readonly PowerShell _powershell;
public PSCore(ConfigService configService)
public PSCore(
IConfigService configService,
ILogger<PSCore> logger)
{
_configService = configService;
_logger = logger;
_connectionInfo = _configService.GetConnectionInfo();
_powershell = PowerShell.Create();
_powershell.AddScript($@"$VerbosePreference = ""Continue"";
@ -36,12 +50,10 @@ namespace Remotely.Agent.Services
}
public string SenderConnectionId { get; private set; }
private static ConcurrentDictionary<string, PSCore> Sessions { get; set; } = new ConcurrentDictionary<string, PSCore>();
// TODO: Turn into cache and factory.
public static PSCore GetCurrent(string senderConnectionId)
{
if (Sessions.TryGetValue(senderConnectionId, out var session))
if (_sessions.TryGetValue(senderConnectionId, out var session))
{
return session;
}
@ -49,7 +61,7 @@ namespace Remotely.Agent.Services
{
session = Program.Services.GetRequiredService<PSCore>();
session.SenderConnectionId = senderConnectionId;
Sessions.AddOrUpdate(senderConnectionId, session, (id, b) => session);
_sessions.AddOrUpdate(senderConnectionId, session, (id, b) => session);
return session;
}
}
@ -73,44 +85,64 @@ namespace Remotely.Agent.Services
public ScriptResult WriteInput(string input)
{
var deviceId = _configService.GetConnectionInfo().DeviceID;
var sw = Stopwatch.StartNew();
_powershell.Streams.ClearStreams();
_powershell.Commands.Clear();
_powershell.AddScript(input);
var results = _powershell.Invoke();
using var ps = PowerShell.Create();
ps.AddScript("$args[0] | Out-String");
ps.AddArgument(results);
var hostOutput = (string)ps.Invoke()[0].BaseObject;
var verboseOut = _powershell.Streams.Verbose.ReadAll().Select(x => x.Message);
var debugOut = _powershell.Streams.Debug.ReadAll().Select(x => x.Message);
var errorOut = _powershell.Streams.Error.ReadAll().Select(x => x.Exception.ToString() + Environment.NewLine + x.ScriptStackTrace);
var infoOut = _powershell.Streams.Information.Select(x => x.MessageData.ToString());
var warningOut = _powershell.Streams.Warning.Select(x => x.Message);
var standardOut = hostOutput.Split(Environment.NewLine)
.Concat(infoOut)
.Concat(debugOut)
.Concat(verboseOut);
var errorAndWarningOut = errorOut.Concat(warningOut).ToArray();
return new ScriptResult()
try
{
DeviceID = _configService.GetConnectionInfo().DeviceID,
SenderConnectionID = SenderConnectionId,
ScriptInput = input,
Shell = Shared.Enums.ScriptingShell.PSCore,
StandardOutput = standardOut.ToArray(),
ErrorOutput = errorAndWarningOut,
RunTime = sw.Elapsed,
HadErrors = _powershell.HadErrors || errorAndWarningOut.Any()
};
_powershell.Streams.ClearStreams();
_powershell.Commands.Clear();
_powershell.AddScript(input);
var results = _powershell.Invoke();
using var ps = PowerShell.Create();
ps.AddScript("$args[0] | Out-String");
ps.AddArgument(results);
var hostOutput = (string)ps.Invoke()[0].BaseObject;
var verboseOut = _powershell.Streams.Verbose.ReadAll().Select(x => x.Message);
var debugOut = _powershell.Streams.Debug.ReadAll().Select(x => x.Message);
var errorOut = _powershell.Streams.Error.ReadAll().Select(x => x.Exception.ToString() + Environment.NewLine + x.ScriptStackTrace);
var infoOut = _powershell.Streams.Information.Select(x => x.MessageData.ToString());
var warningOut = _powershell.Streams.Warning.Select(x => x.Message);
var standardOut = hostOutput.Split(Environment.NewLine)
.Concat(infoOut)
.Concat(debugOut)
.Concat(verboseOut);
var errorAndWarningOut = errorOut.Concat(warningOut).ToArray();
return new ScriptResult()
{
DeviceID = _configService.GetConnectionInfo().DeviceID,
SenderConnectionID = SenderConnectionId,
ScriptInput = input,
Shell = Shared.Enums.ScriptingShell.PSCore,
StandardOutput = standardOut.ToArray(),
ErrorOutput = errorAndWarningOut,
RunTime = sw.Elapsed,
HadErrors = _powershell.HadErrors || errorAndWarningOut.Any()
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while writing input to PSCore.");
return new ScriptResult()
{
DeviceID = deviceId,
SenderConnectionID = SenderConnectionId,
ScriptInput = input,
Shell = Shared.Enums.ScriptingShell.PSCore,
StandardOutput = Array.Empty<string>(),
ErrorOutput = new[] { "Error while writing input." },
RunTime = sw.Elapsed,
HadErrors = true
};
}
}
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using Remotely.Shared;
using Remotely.Shared.Enums;
using Remotely.Shared.Models;
@ -14,14 +15,24 @@ using System.Threading.Tasks;
namespace Remotely.Agent.Services
{
public class ScriptExecutor
public interface IScriptExecutor
{
public ScriptExecutor(ConfigService configService)
Task RunCommandFromApi(ScriptingShell shell, string requestID, string command, string senderUsername, string authToken, HubConnection hubConnection);
Task RunCommandFromTerminal(ScriptingShell shell, string command, string authToken, string senderUsername, string senderConnectionID, ScriptInputType scriptInputType, TimeSpan timeout, HubConnection hubConnection);
Task RunScript(Guid savedScriptId, int scriptRunId, string initiator, ScriptInputType scriptInputType, string authToken);
}
public class ScriptExecutor : IScriptExecutor
{
private readonly IConfigService _configService;
private readonly ILogger<ScriptExecutor> _logger;
public ScriptExecutor(IConfigService configService, ILogger<ScriptExecutor> logger)
{
ConfigService = configService;
_configService = configService;
_logger = logger;
}
private ConfigService ConfigService { get; }
public async Task RunCommandFromApi(ScriptingShell shell,
string requestID,
@ -43,7 +54,7 @@ namespace Remotely.Agent.Services
}
catch (Exception ex)
{
Logger.Write(ex);
_logger.LogError(ex, "Error while running command from API.");
}
}
@ -72,7 +83,7 @@ namespace Remotely.Agent.Services
}
catch (Exception ex)
{
Logger.Write(ex);
_logger.LogError(ex, "Error while running command from terminal.");
await hubConnection.SendAsync("DisplayMessage",
"There was an error executing the command. It has been logged on the client device.",
"Error executing command.",
@ -89,9 +100,13 @@ namespace Remotely.Agent.Services
{
try
{
Logger.Write($"Script run started. Script ID: {savedScriptId}. Script Run: {scriptRunId}. Initiator: {initiator}.");
_logger.LogInformation(
"Script run started. Script ID: {savedScriptId}. Script Run: {scriptRunId}. Initiator: {initiator}.",
savedScriptId,
scriptRunId,
initiator);
var connectionInfo = ConfigService.GetConnectionInfo();
var connectionInfo = _configService.GetConnectionInfo();
var url = $"{connectionInfo.Host}/API/SavedScripts/{savedScriptId}";
using var hc = new HttpClient();
hc.DefaultRequestHeaders.Add("Authorization", authToken);
@ -111,7 +126,7 @@ namespace Remotely.Agent.Services
}
catch (Exception ex)
{
Logger.Write(ex);
_logger.LogError(ex, "Error while running script.");
}
}
@ -156,7 +171,7 @@ namespace Remotely.Agent.Services
}
private async Task<ScriptResult> SendResultsToApi(object result, string authToken)
{
var targetURL = ConfigService.GetConnectionInfo().Host + $"/API/ScriptResults";
var targetURL = _configService.GetConnectionInfo().Host + $"/API/ScriptResults";
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Authorization", authToken);
@ -165,7 +180,7 @@ namespace Remotely.Agent.Services
if (!response.IsSuccessStatusCode)
{
Logger.Write($"Failed to send script results. Status Code: {response.StatusCode}");
_logger.LogError("Failed to send script results. Status Code: {responseStatusCode}", response.StatusCode);
return default;
}

View File

@ -6,7 +6,12 @@ using System.IO;
namespace Remotely.Agent.Services
{
public class Uninstaller
public interface IUninstaller
{
void UninstallAgent();
}
public class Uninstaller : IUninstaller
{
public void UninstallAgent()
{

View File

@ -22,7 +22,7 @@ namespace Remotely.Agent.Services.Windows
private readonly ILogger<AppLauncherWin> _logger;
private readonly string _rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Desktop", EnvironmentHelper.DesktopExecutableFileName);
public AppLauncherWin(ConfigService configService, ILogger<AppLauncherWin> logger)
public AppLauncherWin(IConfigService configService, ILogger<AppLauncherWin> logger)
{
_connectionInfo = configService.GetConnectionInfo();
_logger = logger;

View File

@ -15,7 +15,7 @@ namespace Remotely.Agent.Services.Windows
public class UpdaterWin : IUpdater
{
private readonly SemaphoreSlim _checkForUpdatesLock = new(1, 1);
private readonly ConfigService _configService;
private readonly IConfigService _configService;
private readonly IUpdateDownloader _updateDownloader;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<UpdaterWin> _logger;
@ -25,7 +25,7 @@ namespace Remotely.Agent.Services.Windows
public UpdaterWin(
ConfigService configService,
IConfigService configService,
IUpdateDownloader updateDownloader,
IHttpClientFactory httpClientFactory,
ILogger<UpdaterWin> logger)