Remotely/Agent/Services/ScriptExecutor.cs

223 lines
8.0 KiB
C#

using Immense.RemoteControl.Shared.Extensions;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Remotely.Shared;
using Remotely.Shared.Dtos;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
using Remotely.Shared.Utilities;
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
namespace Remotely.Agent.Services;
public interface IScriptExecutor
{
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 IScriptingShellFactory _scriptingShellFactory;
private readonly ILogger<ScriptExecutor> _logger;
public ScriptExecutor(
IConfigService configService,
IScriptingShellFactory scriptingShellFactory,
ILogger<ScriptExecutor> logger)
{
_configService = configService;
_scriptingShellFactory = scriptingShellFactory;
_logger = logger;
}
public async Task RunCommandFromApi(ScriptingShell shell,
string requestID,
string command,
string senderUsername,
string authToken,
HubConnection hubConnection)
{
try
{
var result = await ExecuteScriptContent(shell, requestID, command, TimeSpan.FromMinutes(AppConstants.ScriptRunExpirationMinutes));
result.InputType = ScriptInputType.Api;
result.SenderUserName = senderUsername;
_ = await SendResultsToApi(result, authToken);
await hubConnection.SendAsync("ScriptResultViaApi", requestID);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while running command from API.");
}
}
public async Task RunCommandFromTerminal(ScriptingShell shell,
string command,
string authToken,
string senderUsername,
string senderConnectionID,
ScriptInputType scriptInputType,
TimeSpan timeout,
HubConnection hubConnection)
{
try
{
var result = await ExecuteScriptContent(shell, senderConnectionID, command, timeout);
result.InputType = scriptInputType;
result.SenderUserName = senderUsername;
var responseResult = await SendResultsToApi(result, authToken);
if (responseResult is null)
{
return;
}
await hubConnection.SendAsync("ScriptResult", responseResult.Id);
}
catch (Exception 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.",
"bg-danger",
senderConnectionID);
}
}
public async Task RunScript(Guid savedScriptId,
int scriptRunId,
string initiator,
ScriptInputType scriptInputType,
string expiringToken)
{
try
{
_logger.LogInformation(
"Script run started. Script ID: {savedScriptId}. Script Run: {scriptRunId}. Initiator: {initiator}.",
savedScriptId,
scriptRunId,
initiator);
var url = $"{_configService.GetConnectionInfo().Host}/API/SavedScripts/{savedScriptId}";
using var hc = new HttpClient();
hc.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken);
var response = await hc.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return;
}
_logger.LogWarning("Failed to get saved script. Status Code: {responseStatusCode}", response.StatusCode);
return;
}
var savedScript = await response.Content.ReadFromJsonAsync<SavedScript>();
if (savedScript is null)
{
_logger.LogWarning("Failed to deserialize saved script.");
return;
}
if (string.IsNullOrWhiteSpace(savedScript.Content))
{
_logger.LogWarning("Script content is empty. Aborting script run.");
return;
}
var result = await ExecuteScriptContent(savedScript.Shell,
Guid.NewGuid().ToString(),
savedScript.Content,
TimeSpan.FromMinutes(AppConstants.ScriptRunExpirationMinutes));
result.SenderUserName = initiator;
result.ScriptRunId = scriptRunId;
result.InputType = scriptInputType;
result.SavedScriptId = savedScriptId;
_ = await SendResultsToApi(result, expiringToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while running script.");
}
}
private async Task<ScriptResultDto> ExecuteScriptContent(
ScriptingShell shell,
string terminalSessionId,
string command,
TimeSpan timeout)
{
switch (shell)
{
case ScriptingShell.PSCore:
return await _scriptingShellFactory
.GetOrCreatePsCoreShell(terminalSessionId)
.WriteInput(command);
case ScriptingShell.WinPS:
if (EnvironmentHelper.IsWindows)
{
var instance = await _scriptingShellFactory
.GetOrCreateExternalShell(ScriptingShell.WinPS, terminalSessionId);
return await instance.WriteInput(command, timeout);
}
break;
case ScriptingShell.CMD:
if (EnvironmentHelper.IsWindows)
{
var instance = await _scriptingShellFactory
.GetOrCreateExternalShell(ScriptingShell.CMD, terminalSessionId);
return await instance.WriteInput(command, timeout);
}
break;
case ScriptingShell.Bash:
{
var instance = await _scriptingShellFactory
.GetOrCreateExternalShell(ScriptingShell.Bash, terminalSessionId);
return await instance.WriteInput(command, timeout);
}
default:
break;
}
throw new InvalidOperationException($"Unknown shell type: {shell}");
}
private async Task<ScriptResultResponse?> SendResultsToApi(ScriptResultDto result, string expiringToken)
{
var targetURL = _configService.GetConnectionInfo().Host + $"/API/ScriptResults";
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken);
using var response = await httpClient.PostAsJsonAsync(targetURL, result);
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Failed to send script results. Status Code: {responseStatusCode}", response.StatusCode);
return default;
}
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ScriptResultResponse>(content, JsonSerializerHelper.CaseInsensitiveOptions);
}
}