Remotely/Agent/Services/PsCoreShell.cs

158 lines
5.2 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.Agent.Interfaces;
using Remotely.Shared.Dtos;
using Remotely.Shared.Models;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Management.Automation;
using System.Threading.Tasks;
namespace Remotely.Agent.Services;
public interface IPsCoreShell : IDisposable, IScriptingShell
{
string? SenderConnectionId { get; set; }
CommandCompletion GetCompletions(string inputText, int currentIndex, bool? forward);
Task<ScriptResultDto> WriteInput(string input);
}
public class PsCoreShell : IPsCoreShell
{
private readonly IConfigService _configService;
private readonly ConnectionInfo _connectionInfo;
private readonly ILogger<PsCoreShell> _logger;
private readonly PowerShell _powershell;
private bool _disposedValue;
private CommandCompletion? _lastCompletion;
private string? _lastInputText;
public PsCoreShell(
IConfigService configService,
ILogger<PsCoreShell> logger)
{
_configService = configService;
_connectionInfo = _configService.GetConnectionInfo();
_logger = logger;
_powershell = PowerShell.Create();
_powershell.AddScript($@"$VerbosePreference = ""Continue"";
$DebugPreference = ""Continue"";
$InformationPreference = ""Continue"";
$WarningPreference = ""Continue"";
$env:DeviceId = ""{_connectionInfo.DeviceID}"";
$env:ServerUrl = ""{_connectionInfo.Host}""");
_powershell.Invoke();
}
public bool IsDisposed => _disposedValue;
public string? SenderConnectionId { get; set; }
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public CommandCompletion GetCompletions(string inputText, int currentIndex, bool? forward)
{
if (_lastCompletion is null ||
inputText != _lastInputText)
{
_lastInputText = inputText;
_lastCompletion = CommandCompletion.CompleteInput(inputText, currentIndex, new(), _powershell);
}
if (forward.HasValue)
{
_lastCompletion.GetNextResult(forward.Value);
}
return _lastCompletion;
}
public async Task<ScriptResultDto> WriteInput(string input)
{
var deviceId = _configService.GetConnectionInfo().DeviceID;
var sw = Stopwatch.StartNew();
try
{
_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 result = await ps.InvokeAsync();
var hostOutput = result.Count > 0 ?
$"{result[0].BaseObject}" :
string.Empty;
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)
.Select(x => $"{x}");
var errorAndWarningOut = errorOut.Concat(warningOut).ToArray();
return new ScriptResultDto()
{
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 ScriptResultDto()
{
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
};
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_powershell.Dispose();
}
_disposedValue = true;
}
}
}