diff --git a/Agent/Program.cs b/Agent/Program.cs index 224bbaeb1..1fab73ae6 100644 --- a/Agent/Program.cs +++ b/Agent/Program.cs @@ -94,12 +94,12 @@ private static void RegisterServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddHostedService(services => services.GetRequiredService()); - services.AddScoped(); - services.AddTransient(); - services.AddTransient(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddTransient(); + services.AddTransient(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Agent/Services/AgentHubConnection.cs b/Agent/Services/AgentHubConnection.cs index f876654fc..d3766d112 100644 --- a/Agent/Services/AgentHubConnection.cs +++ b/Agent/Services/AgentHubConnection.cs @@ -29,20 +29,15 @@ public interface IAgentHubConnection 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 _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 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable 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, diff --git a/Agent/Services/ChatClientService.cs b/Agent/Services/ChatClientService.cs index fc00d9af0..6aaf3a155 100644 --- a/Agent/Services/ChatClientService.cs +++ b/Agent/Services/ChatClientService.cs @@ -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 @@ 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 _logger; private readonly MemoryCache _chatClients = new("ChatClients"); - private readonly SemaphoreSlim _messageLock = new(1,1); - - public ChatClientService(IAppLauncher appLauncher) - { - _appLauncher = appLauncher; - } + private readonly SemaphoreSlim _messageLock = new(1, 1); - private CacheItemPolicy CacheItemPolicy { get; } = new() + private readonly CacheItemPolicy _cacheItemPolicy = new() { SlidingExpiration = TimeSpan.FromMinutes(10), RemovedCallback = new CacheEntryRemovedCallback(args => @@ -48,6 +50,13 @@ public ChatClientService(IAppLauncher appLauncher) }) }; + public ChatClientService( + IAppLauncher appLauncher, + ILogger logger) + { + _appLauncher = appLauncher; + _logger = logger; + } public async Task SendMessage( string senderName, @@ -60,7 +69,7 @@ public async Task SendMessage( { 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 @@ public async Task SendMessage( 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 @@ public async Task SendMessage( 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 @@ public async Task SendMessage( } catch (Exception ex) { - Logger.Write(ex); + _logger.LogError(ex, "Error while sending chat message."); } finally { diff --git a/Agent/Services/ConfigService.cs b/Agent/Services/ConfigService.cs index 6f406aed8..ed895bf17 100644 --- a/Agent/Services/ConfigService.cs +++ b/Agent/Services/ConfigService.cs @@ -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 @@ 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 _logger; + + public ConfigService(ILogger logger) + { + _logger = logger; + } private Dictionary _commandLineArgs; private Dictionary CommandLineArgs @@ -74,7 +87,7 @@ public ConnectionInfo GetConnectionInfo() { 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(File.ReadAllText("ConnectionInfo.json")); diff --git a/Agent/Services/DeviceInfoGeneratorBase.cs b/Agent/Services/DeviceInfoGeneratorBase.cs index 5f58c9e9d..fd5df5051 100644 --- a/Agent/Services/DeviceInfoGeneratorBase.cs +++ b/Agent/Services/DeviceInfoGeneratorBase.cs @@ -13,7 +13,7 @@ namespace Remotely.Agent.Services { - public class DeviceInfoGeneratorBase + public abstract class DeviceInfoGeneratorBase { protected readonly ILogger _logger; diff --git a/Agent/Services/ExternalScriptingShell.cs b/Agent/Services/ExternalScriptingShell.cs index 83915c400..e75baf5d6 100644 --- a/Agent/Services/ExternalScriptingShell.cs +++ b/Agent/Services/ExternalScriptingShell.cs @@ -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 _sessions = new(); - private readonly ConfigService _configService; + private readonly IConfigService _configService; + private readonly ILogger _logger; private string _lineEnding; private ScriptingShell _shell; - public ExternalScriptingShell(ConfigService configService) + public ExternalScriptingShell( + IConfigService configService, + ILogger logger) { _configService = configService; + _logger = logger; } private string ErrorOut { get; set; } @@ -46,6 +51,7 @@ public ExternalScriptingShell(ConfigService configService) 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 @@ public ScriptResult WriteInput(string input, TimeSpan timeout) } 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. diff --git a/Agent/Services/Linux/AppLauncherLinux.cs b/Agent/Services/Linux/AppLauncherLinux.cs index 33ed94932..2aeb5880e 100644 --- a/Agent/Services/Linux/AppLauncherLinux.cs +++ b/Agent/Services/Linux/AppLauncherLinux.cs @@ -24,7 +24,7 @@ public class AppLauncherLinux : IAppLauncher private readonly ILogger _logger; public AppLauncherLinux( - ConfigService configService, + IConfigService configService, IProcessInvoker processInvoker, ILogger logger) { diff --git a/Agent/Services/Linux/UpdaterLinux.cs b/Agent/Services/Linux/UpdaterLinux.cs index 3748fe1b5..f4b8f500d 100644 --- a/Agent/Services/Linux/UpdaterLinux.cs +++ b/Agent/Services/Linux/UpdaterLinux.cs @@ -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 _logger; @@ -29,7 +29,7 @@ public class UpdaterLinux : IUpdater private DateTimeOffset _lastUpdateFailure; public UpdaterLinux( - ConfigService configService, + IConfigService configService, IUpdateDownloader updateDownloader, IHttpClientFactory httpClientFactory, ILogger logger) diff --git a/Agent/Services/MacOS/UpdaterMac.cs b/Agent/Services/MacOS/UpdaterMac.cs index 1f6574951..a70463e88 100644 --- a/Agent/Services/MacOS/UpdaterMac.cs +++ b/Agent/Services/MacOS/UpdaterMac.cs @@ -21,7 +21,7 @@ public class UpdaterMac : IUpdater { 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 _logger; @@ -30,7 +30,7 @@ public class UpdaterMac : IUpdater private readonly System.Timers.Timer _updateTimer = new(TimeSpan.FromHours(6).TotalMilliseconds); public UpdaterMac( - ConfigService configService, + IConfigService configService, IUpdateDownloader updateDownloader, IHttpClientFactory httpClientFactory, ILogger logger) diff --git a/Agent/Services/PSCore.cs b/Agent/Services/PSCore.cs index 8f88b2b71..c7af5f9d8 100644 --- a/Agent/Services/PSCore.cs +++ b/Agent/Services/PSCore.cs @@ -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.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 _sessions = new ConcurrentDictionary(); + private readonly IConfigService _configService; private readonly ConnectionInfo _connectionInfo; + private readonly ILogger _logger; + private readonly PowerShell _powershell; private CommandCompletion _lastCompletion; private string _lastInputText; - private readonly PowerShell _powershell; - - public PSCore(ConfigService configService) + public PSCore( + IConfigService configService, + ILogger logger) { _configService = configService; + _logger = logger; _connectionInfo = _configService.GetConnectionInfo(); - + _powershell = PowerShell.Create(); _powershell.AddScript($@"$VerbosePreference = ""Continue""; @@ -36,12 +50,10 @@ public PSCore(ConfigService configService) } public string SenderConnectionId { get; private set; } - - private static ConcurrentDictionary Sessions { get; set; } = new ConcurrentDictionary(); - + // 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 @@ public static PSCore GetCurrent(string senderConnectionId) { session = Program.Services.GetRequiredService(); session.SenderConnectionId = senderConnectionId; - Sessions.AddOrUpdate(senderConnectionId, session, (id, b) => session); + _sessions.AddOrUpdate(senderConnectionId, session, (id, b) => session); return session; } } @@ -73,44 +85,64 @@ public CommandCompletion GetCompletions(string inputText, int currentIndex, bool 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(); - + try + { - return new ScriptResult() + _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) { - 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() - }; + _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(), + ErrorOutput = new[] { "Error while writing input." }, + RunTime = sw.Elapsed, + HadErrors = true + }; + } } } } diff --git a/Agent/Services/ScriptExecutor.cs b/Agent/Services/ScriptExecutor.cs index a4149cb10..bc8013c74 100644 --- a/Agent/Services/ScriptExecutor.cs +++ b/Agent/Services/ScriptExecutor.cs @@ -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 @@ 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 _logger; + + public ScriptExecutor(IConfigService configService, ILogger logger) { - ConfigService = configService; + _configService = configService; + _logger = logger; } - private ConfigService ConfigService { get; } public async Task RunCommandFromApi(ScriptingShell shell, string requestID, @@ -43,7 +54,7 @@ public async Task RunCommandFromApi(ScriptingShell shell, } catch (Exception ex) { - Logger.Write(ex); + _logger.LogError(ex, "Error while running command from API."); } } @@ -72,7 +83,7 @@ public async Task RunCommandFromTerminal(ScriptingShell shell, } 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 @@ public async Task RunScript(Guid savedScriptId, { 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 @@ public async Task RunScript(Guid savedScriptId, } catch (Exception ex) { - Logger.Write(ex); + _logger.LogError(ex, "Error while running script."); } } @@ -156,7 +171,7 @@ private ScriptResult ExecuteScriptContent(ScriptingShell shell, } private async Task 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 @@ private async Task SendResultsToApi(object result, string authToke 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; } diff --git a/Agent/Services/Uninstaller.cs b/Agent/Services/Uninstaller.cs index d968dea1a..1d4dca03f 100644 --- a/Agent/Services/Uninstaller.cs +++ b/Agent/Services/Uninstaller.cs @@ -6,7 +6,12 @@ namespace Remotely.Agent.Services { - public class Uninstaller + public interface IUninstaller + { + void UninstallAgent(); + } + + public class Uninstaller : IUninstaller { public void UninstallAgent() { diff --git a/Agent/Services/Windows/AppLauncherWin.cs b/Agent/Services/Windows/AppLauncherWin.cs index 5b24089c5..b7a943424 100644 --- a/Agent/Services/Windows/AppLauncherWin.cs +++ b/Agent/Services/Windows/AppLauncherWin.cs @@ -22,7 +22,7 @@ public class AppLauncherWin : IAppLauncher private readonly ILogger _logger; private readonly string _rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Desktop", EnvironmentHelper.DesktopExecutableFileName); - public AppLauncherWin(ConfigService configService, ILogger logger) + public AppLauncherWin(IConfigService configService, ILogger logger) { _connectionInfo = configService.GetConnectionInfo(); _logger = logger; diff --git a/Agent/Services/Windows/UpdaterWin.cs b/Agent/Services/Windows/UpdaterWin.cs index 34538a883..68767131b 100644 --- a/Agent/Services/Windows/UpdaterWin.cs +++ b/Agent/Services/Windows/UpdaterWin.cs @@ -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 _logger; @@ -25,7 +25,7 @@ public class UpdaterWin : IUpdater public UpdaterWin( - ConfigService configService, + IConfigService configService, IUpdateDownloader updateDownloader, IHttpClientFactory httpClientFactory, ILogger logger)