366 lines
14 KiB
C#
366 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.AccessControl;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
using C4IT.FASD.Base;
|
|
using C4IT.MultiLanguage;
|
|
using C4IT.Security;
|
|
|
|
using C4IT.Logging;
|
|
using static C4IT.Logging.cLogManager;
|
|
|
|
namespace FasdCockpitBase
|
|
{
|
|
public class cExternalToolExecutor
|
|
{
|
|
public interface iNamedParameter
|
|
{
|
|
string Title { get; set; }
|
|
string GetValue();
|
|
(string Title, string Value) GetTitleValuePair();
|
|
}
|
|
|
|
[DllImport("shell32.dll", EntryPoint = "ShellExecute")]
|
|
public static extern long ShellExecute(int hwnd, string cmd, string file, string param1, string param2, int swmode);
|
|
|
|
private static cCredential RemoteToolAlternateCredentials;
|
|
|
|
public class cProcessResult
|
|
{
|
|
public cProcessResult(int returnCode) { ReturnCode = returnCode; }
|
|
public int ReturnCode = -1;
|
|
public string StandardOutput = null;
|
|
public string StandardError = null;
|
|
}
|
|
|
|
static protected cCredential GetRemoteToolCredentials(string Title)
|
|
{
|
|
if (RemoteToolAlternateCredentials == null)
|
|
{
|
|
var Caption = cMultiLanguageSupport.GetItem("RemoteTool.Credentials.Caption");
|
|
Caption = string.Format(Caption, Title);
|
|
var Message = cMultiLanguageSupport.GetItem("RemoteTool.Credentials.Message");
|
|
|
|
RemoteToolAlternateCredentials = cInputCredentials.GetCredentials(Caption, Message);
|
|
}
|
|
|
|
return RemoteToolAlternateCredentials;
|
|
}
|
|
|
|
static protected void AddFileSecurity(string fileName, string account, FileSystemRights rights, AccessControlType controlType)
|
|
{
|
|
try
|
|
{
|
|
FileSecurity fSecurity = File.GetAccessControl(fileName);
|
|
fSecurity.AddAccessRule(new FileSystemAccessRule(account, rights, controlType));
|
|
File.SetAccessControl(fileName, fSecurity);
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
}
|
|
}
|
|
|
|
static protected string ReplaceEnvironmentVariables(string input)
|
|
{
|
|
var output = input;
|
|
|
|
try
|
|
{
|
|
var specialFolders = Enum.GetValues(typeof(Environment.SpecialFolder)).Cast<Environment.SpecialFolder>();
|
|
|
|
foreach (var specialFolder in specialFolders)
|
|
{
|
|
var specialFolderName = "%" + specialFolder.ToString() + "%";
|
|
var specialFolderPath = Environment.GetFolderPath(specialFolder);
|
|
output = output.Replace(specialFolderName, specialFolderPath);
|
|
}
|
|
|
|
output = Environment.ExpandEnvironmentVariables(output);
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
static protected async Task<cProcessResult> RunProcessAsync(string Cmd, string Params, cCredential Credentials, bool Hidden)
|
|
{
|
|
try
|
|
{
|
|
cProcessResult processResult = new cProcessResult(0);
|
|
|
|
var process = createProcessInstance(Cmd, Params, Credentials, true, Hidden);
|
|
|
|
TaskCompletionSource<int> _resultAwaiter = null;
|
|
_resultAwaiter = new TaskCompletionSource<int>();
|
|
EventHandler resultEventHandler = null;
|
|
resultEventHandler = (sender, args) =>
|
|
{
|
|
process.Exited -= resultEventHandler;
|
|
_resultAwaiter.SetResult(process.ExitCode);
|
|
};
|
|
process.Exited += resultEventHandler;
|
|
process.EnableRaisingEvents = true;
|
|
|
|
try
|
|
{
|
|
process.Start();
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E, LogLevels.Warning);
|
|
return new cProcessResult(E.HResult);
|
|
}
|
|
|
|
var t1 = process.StandardOutput.ReadToEndAsync();
|
|
var t2 = process.StandardError.ReadToEndAsync();
|
|
var t3 = _resultAwaiter.Task;
|
|
|
|
await Task.WhenAll(t1, t2, t3);
|
|
|
|
processResult.StandardOutput = (await t1);
|
|
processResult.StandardError = (await t2);
|
|
processResult.ReturnCode = (await t3);
|
|
|
|
return processResult;
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
return new cProcessResult(E.HResult);
|
|
}
|
|
}
|
|
|
|
static protected cProcessResult StartProcessAsync(string Cmd, string Params, cCredential Credentials, bool Hidden)
|
|
{
|
|
try
|
|
{
|
|
using (var process = createProcessInstance(Cmd, Params, Credentials, false, Hidden))
|
|
{
|
|
var _s = process.Start();
|
|
}
|
|
return new cProcessResult(0);
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E, LogLevels.Warning);
|
|
return new cProcessResult(E.HResult);
|
|
}
|
|
}
|
|
|
|
static protected Process createProcessInstance(string Cmd, string Params, cCredential Credentials, bool Wait, bool Hidden)
|
|
{
|
|
try
|
|
{
|
|
var processInformation = new ProcessStartInfo(Cmd, Params);
|
|
processInformation.UseShellExecute = false;
|
|
if (Credentials != null)
|
|
{
|
|
processInformation.LoadUserProfile = true;
|
|
processInformation.UserName = Credentials.User;
|
|
processInformation.Password = Credentials.Password;
|
|
processInformation.Domain = Credentials.Domain;
|
|
}
|
|
|
|
if (Hidden)
|
|
{
|
|
processInformation.CreateNoWindow = true;
|
|
processInformation.WindowStyle = ProcessWindowStyle.Hidden;
|
|
}
|
|
|
|
if (Wait)
|
|
{
|
|
processInformation.RedirectStandardOutput = true;
|
|
processInformation.RedirectStandardError = true;
|
|
}
|
|
|
|
var process = new Process() { StartInfo = processInformation };
|
|
|
|
return process;
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static public string ReplaceParameters(string input, Dictionary<string, iNamedParameter> NamedParameters, bool ReplaceEnvironment, Dictionary<cAdjustableParameter, object> ParameterDictionary = null)
|
|
{
|
|
string output = input;
|
|
try
|
|
{
|
|
if (input != null)
|
|
{
|
|
if (ParameterDictionary != null)
|
|
{
|
|
foreach (var parameter in ParameterDictionary)
|
|
{
|
|
var _str = parameter.Key.GetValueString(parameter.Value);
|
|
output = output.Replace("%" + parameter.Key.ParameterName + "%", _str);
|
|
}
|
|
}
|
|
|
|
if (NamedParameters != null)
|
|
foreach (var namedParameter in NamedParameters)
|
|
{
|
|
try
|
|
{
|
|
var parameterName = namedParameter.Key;
|
|
|
|
var namedParameterKeyValuePair = namedParameter.Value.GetTitleValuePair();
|
|
var parameterLabel = namedParameterKeyValuePair.Title;
|
|
var parameterValue = namedParameterKeyValuePair.Value;
|
|
|
|
output = output.Replace("%" + parameterName + ".Label%", parameterLabel);
|
|
output = output.Replace("%" + parameterName + ".Value%", parameterValue);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
if (ReplaceEnvironment)
|
|
output = ReplaceEnvironmentVariables(output);
|
|
}
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
}
|
|
return output;
|
|
}
|
|
|
|
public static async Task<cProcessResult> StartLocalActionAsync(cFasdQuickActionLocal localAction, Dictionary<string, iNamedParameter> NamedParameters, string ProgramTitle, Dictionary<cAdjustableParameter, object> ParameterDictionary = null)
|
|
{
|
|
cProcessResult _resultProcess = new cProcessResult(0);
|
|
|
|
cCredential Credentials = null;
|
|
if (localAction.StartWithAlternateCredentials)
|
|
Credentials = GetRemoteToolCredentials(ProgramTitle);
|
|
|
|
const string constPowershellPath = @"%System%\WindowsPowerShell\v1.0\powershell.exe";
|
|
|
|
string parameters = null;
|
|
string cmd = null;
|
|
string scriptName = null;
|
|
bool dontUseShell = true;
|
|
|
|
bool isHidden = false;
|
|
bool isScript = false;
|
|
bool doWait = (localAction.ColumnOutputFormattings?.Count > 0);
|
|
|
|
if (localAction is cFasdQuickActionLocalScript quickActionScript)
|
|
{
|
|
isScript = true;
|
|
isHidden = true;
|
|
doWait = true;
|
|
|
|
cmd = constPowershellPath;
|
|
scriptName = Path.GetTempFileName();
|
|
if (File.Exists(scriptName))
|
|
File.Delete(scriptName);
|
|
scriptName += ".ps1";
|
|
|
|
if (quickActionScript.IsBase64)
|
|
File.WriteAllBytes(scriptName, quickActionScript.ScriptBinary);
|
|
else
|
|
File.WriteAllText(scriptName, quickActionScript.Script, Encoding.Unicode);
|
|
|
|
if (localAction.StartWithAlternateCredentials && (Credentials != null))
|
|
AddFileSecurity(scriptName, Credentials.GetAccount(true), FileSystemRights.Read, AccessControlType.Allow);
|
|
|
|
parameters = $"-NoLogo -ExecutionPolicy bypass -file \"{scriptName}\"";
|
|
|
|
if (localAction.Parameters != null)
|
|
parameters += " " + localAction.Parameters;
|
|
}
|
|
else if (localAction is cFasdQuickActionLocalCmd quickActionCmd)
|
|
{
|
|
cmd = quickActionCmd.Cmd;
|
|
parameters = quickActionCmd.Parameters;
|
|
dontUseShell = quickActionCmd.DontUseShell;
|
|
}
|
|
else
|
|
return _resultProcess;
|
|
|
|
cmd = ReplaceEnvironmentVariables(cmd);
|
|
|
|
if (parameters != null)
|
|
parameters = ReplaceParameters(parameters, NamedParameters, true, ParameterDictionary);
|
|
|
|
try
|
|
{
|
|
if (!dontUseShell && !localAction.StartWithAlternateCredentials)
|
|
{
|
|
var PRet = (int)ShellExecute(0, "open", cmd, parameters, null, 5);
|
|
|
|
/*
|
|
* The Microsoft docs states as following regarding the return value of a ShellExecute:
|
|
* https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea
|
|
*
|
|
* If the function succeeds, it returns a value greater than 32.
|
|
* If the function fails, it returns an error value that indicates the cause of the failure.
|
|
* The return value is cast as an HINSTANCE for backward compatibility with 16-bit Windows applications. It is not a true HINSTANCE, however.
|
|
* It can be cast only to an INT_PTR and compared to either 32 or the following error codes below.
|
|
*/
|
|
const int highestFailureReturnCode = 32;
|
|
|
|
// Our internal cPorcessResult is built that way a ReturnCode of 0 is considered as successfull.
|
|
// This has to be mapped to the HINSTANCE result code above.
|
|
if (PRet > highestFailureReturnCode)
|
|
PRet = 0;
|
|
|
|
_resultProcess = new cProcessResult(PRet);
|
|
}
|
|
else
|
|
{
|
|
await Task.Run(async () =>
|
|
{
|
|
if (localAction.RequireAdministrator && localAction.StartWithAlternateCredentials)
|
|
{
|
|
cmd = ReplaceEnvironmentVariables("%System%\\WindowsPowerShell\\v1.0\\powershell.exe");
|
|
var argumentList = parameters.Replace("\"", "\"\"");
|
|
var Cmd = $"Start-Process \"{cmd}\" -WindowStyle Hidden -Wait -Verb \"runas\" -ArgumentList \"{argumentList}\"";
|
|
var parameterByteArray = Encoding.Unicode.GetBytes(Cmd);
|
|
parameters = Convert.ToBase64String(parameterByteArray);
|
|
parameters = "-NonInteractive -NoLogo -ExecutionPolicy bypass -EncodedCommand " + parameters;
|
|
isHidden = true;
|
|
}
|
|
|
|
if (doWait)
|
|
_resultProcess = await RunProcessAsync(cmd, parameters, Credentials, isHidden);
|
|
else
|
|
_resultProcess = StartProcessAsync(cmd, parameters, Credentials, isHidden);
|
|
});
|
|
}
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
}
|
|
|
|
if (isScript)
|
|
{
|
|
try
|
|
{
|
|
File.Delete(scriptName);
|
|
}
|
|
catch { }
|
|
;
|
|
}
|
|
|
|
return _resultProcess;
|
|
}
|
|
|
|
}
|
|
}
|