mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-01-05 23:09:45 -06:00
FF-1698: C# scripts!
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using BasicNodes.Scripting;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
@@ -9,26 +10,26 @@ namespace FileFlows.BasicNodes.Scripting;
|
||||
/// <summary>
|
||||
/// Flow element that executes a bat script
|
||||
/// </summary>
|
||||
public class BatScript : Node
|
||||
public class BatScript : ScriptBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override int Inputs => 1;
|
||||
/// <inheritdoc />
|
||||
public override int Outputs => 2;
|
||||
/// <inheritdoc />
|
||||
public override FlowElementType Type => FlowElementType.Process;
|
||||
/// <inheritdoc />
|
||||
public override string Icon => "svg:bat";
|
||||
/// <inheritdoc />
|
||||
public override bool FailureNode => true;
|
||||
/// <inheritdoc />
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/bat-script";
|
||||
/// <inheritdoc />
|
||||
protected override ScriptLanguage Language => ScriptLanguage.Batch;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the code to execute
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DefaultValue(@"REM This is a template batch file
|
||||
[DefaultValue(@"
|
||||
REM A PowerShell script can communicate with FileFlows to determine which output to call next by using exit codes.
|
||||
REM Exit codes are zero-based, so:
|
||||
REM Exit Code 0 corresponds to Output 1
|
||||
REM Exit Code 1 corresponds to Output 2
|
||||
REM Exit Code 2 corresponds to Output 3
|
||||
REM and so on. Exit codes outside the defined range will be treated as a failure output.
|
||||
|
||||
REM Replace {file.FullName} and {file.Orig.FullName} with actual values
|
||||
SET WorkingFile={file.FullName}
|
||||
@@ -45,82 +46,6 @@ REM copy ""%WorkingFile%"" ""C:\Backup\%~nxWorkingFile%""
|
||||
REM Set the exit code to 0
|
||||
EXIT /B 0
|
||||
")]
|
||||
[Code(1, "bat")]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Code))
|
||||
{
|
||||
args.FailureReason = "No code specified in .bat script";
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1; // no code, flow cannot continue doesn't know what to do
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows() == false)
|
||||
{
|
||||
args.FailureReason = "Cannot run a .bat file on a non Windows system";
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var batFile = System.IO.Path.Combine(args.TempPath, Guid.NewGuid() + ".bat");
|
||||
|
||||
try
|
||||
{
|
||||
var code = "@echo off" + Environment.NewLine + args.ReplaceVariables(Code);
|
||||
args.Logger?.ILog("Executing code: \n" + code);
|
||||
System.IO.File.WriteAllText(batFile, code);
|
||||
args.Logger?.ILog($"Temporary bat file created: {batFile}");
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = batFile,
|
||||
WorkingDirectory = args.TempPath,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo };
|
||||
process.Start();
|
||||
|
||||
string standardOutput = process.StandardOutput.ReadToEnd();
|
||||
string standardError = process.StandardError.ReadToEnd();
|
||||
|
||||
process.WaitForExit();
|
||||
int exitCode = process.ExitCode;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(standardOutput) == false)
|
||||
args.Logger?.ILog($"Standard Output:\n{standardOutput}");
|
||||
if(string.IsNullOrWhiteSpace(standardError) == false)
|
||||
args.Logger?.WLog($"Standard Error:\n{standardError}");
|
||||
args.Logger?.ILog($"Exit Code: {exitCode}");
|
||||
|
||||
return exitCode == 0 ? 1 : 2;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.FailureReason = "Failed executing bat script: " + ex.Message;
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(batFile))
|
||||
{
|
||||
System.IO.File.Delete(batFile);
|
||||
args.Logger?.ILog($"Temporary bat file deleted: {batFile}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.Logger?.WLog($"Failed to delete temporary bat file: {batFile}. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
[Code(2, "bat")]
|
||||
public override string Code { get; set; }
|
||||
}
|
||||
43
BasicNodes/Scripting/CSharpScript.cs
Normal file
43
BasicNodes/Scripting/CSharpScript.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
namespace BasicNodes.Scripting;
|
||||
|
||||
/// <summary>
|
||||
/// Flow element that executes a CSharp script
|
||||
/// </summary>
|
||||
public class CSharpScript : ScriptBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Icon => "svg:cs";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/charp-script";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ScriptLanguage Language => ScriptLanguage.CSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the code to execute
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DefaultValue(@"
|
||||
// A C# script will have full access to the executing flow.
|
||||
// Return the output to call next
|
||||
|
||||
// Replace these variables with actual values
|
||||
string workingFile = Variables.file.FullName;
|
||||
string originalFile = Variables.file.Orig.FullName;
|
||||
|
||||
// Example code using the variables
|
||||
Console.WriteLine($""Working on file: {workingFile}"");
|
||||
Console.WriteLine($""Original file location: {originalFile}"");
|
||||
|
||||
// Add your actual C# code below
|
||||
return 1;
|
||||
")]
|
||||
[Code(2, "csharp")]
|
||||
public override string Code { get; set; }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using BasicNodes.Scripting;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
@@ -9,26 +10,27 @@ namespace FileFlows.BasicNodes.Scripting;
|
||||
/// <summary>
|
||||
/// Flow element that executes a PowerShell script
|
||||
/// </summary>
|
||||
public class PowerShellScript : Node
|
||||
public class PowerShellScript : ScriptBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override int Inputs => 1;
|
||||
/// <inheritdoc />
|
||||
public override int Outputs => 2;
|
||||
/// <inheritdoc />
|
||||
public override FlowElementType Type => FlowElementType.Process;
|
||||
/// <inheritdoc />
|
||||
public override string Icon => "svg:ps1";
|
||||
/// <inheritdoc />
|
||||
public override bool FailureNode => true;
|
||||
/// <inheritdoc />
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/powershell-script";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ScriptLanguage Language => ScriptLanguage.PowerShell;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the code to execute
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DefaultValue(@"# This is a template PowerShell script
|
||||
[DefaultValue(@"
|
||||
# A PowerShell script can communicate with FileFlows to determine which output to call next by using exit codes.
|
||||
# Exit codes are zero-based, so:
|
||||
# Exit Code 0 corresponds to Output 1
|
||||
# Exit Code 1 corresponds to Output 2
|
||||
# Exit Code 2 corresponds to Output 3
|
||||
# and so on. Exit codes outside the defined range will be treated as a failure output.
|
||||
|
||||
# Replace {file.FullName} and {file.Orig.FullName} with actual values
|
||||
$WorkingFile = '{file.FullName}'
|
||||
@@ -45,83 +47,6 @@ Write-Output ""Original file location: $OriginalFile""
|
||||
# Set the exit code to 0
|
||||
exit 0
|
||||
")]
|
||||
[Code(1, "powershell")]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Code))
|
||||
{
|
||||
args.FailureReason = "No code specified in PowerShell script";
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1; // no code, flow cannot continue doesn't know what to do
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows() == false)
|
||||
{
|
||||
args.FailureReason = "Cannot run a PowerShell file on a non Windows system";
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var ps1File = System.IO.Path.Combine(args.TempPath, Guid.NewGuid() + ".ps1");
|
||||
|
||||
try
|
||||
{
|
||||
var code = args.ReplaceVariables(Code);
|
||||
args.Logger?.ILog("Executing code: \n" + code);
|
||||
System.IO.File.WriteAllText(ps1File, code);
|
||||
args.Logger?.ILog($"Temporary PowerShell file created: {ps1File}");
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = $"-NoProfile -ExecutionPolicy Bypass -File \"{ps1File}\"",
|
||||
WorkingDirectory = args.TempPath,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo };
|
||||
process.Start();
|
||||
|
||||
string standardOutput = process.StandardOutput.ReadToEnd();
|
||||
string standardError = process.StandardError.ReadToEnd();
|
||||
|
||||
process.WaitForExit();
|
||||
int exitCode = process.ExitCode;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(standardOutput) == false)
|
||||
args.Logger?.ILog($"Standard Output:\n{standardOutput}");
|
||||
if(string.IsNullOrWhiteSpace(standardError) == false)
|
||||
args.Logger?.WLog($"Standard Error:\n{standardError}");
|
||||
args.Logger?.ILog($"Exit Code: {exitCode}");
|
||||
|
||||
return exitCode == 0 ? 1 : 2;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.FailureReason = "Failed executing PowerShell script: " + ex.Message;
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(ps1File))
|
||||
{
|
||||
System.IO.File.Delete(ps1File);
|
||||
args.Logger?.ILog($"Temporary ps1 file deleted: {ps1File}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.Logger?.WLog($"Failed to delete temporary ps1 file: {ps1File}. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
[Code(2, "powershell")]
|
||||
public override string Code { get; set; }
|
||||
}
|
||||
70
BasicNodes/Scripting/ScriptBase.cs
Normal file
70
BasicNodes/Scripting/ScriptBase.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.ComponentModel;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
namespace BasicNodes.Scripting;
|
||||
|
||||
/// <summary>
|
||||
/// Base for a script
|
||||
/// </summary>
|
||||
public abstract class ScriptBase : Node
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override int Inputs => 1;
|
||||
/// <inheritdoc />
|
||||
public override FlowElementType Type => FlowElementType.Process;
|
||||
/// <inheritdoc />
|
||||
public override bool FailureNode => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of outputs
|
||||
/// </summary>
|
||||
[DefaultValue(1)]
|
||||
[NumberInt(1)]
|
||||
public new int Outputs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language of this script
|
||||
/// </summary>
|
||||
protected abstract ScriptLanguage Language { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the code of the script
|
||||
/// </summary>
|
||||
public virtual string Code { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Code))
|
||||
{
|
||||
args.FailureReason = $"No code specified in {Language} script";
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1; // no code, flow cannot continue doesn't know what to do
|
||||
}
|
||||
|
||||
var result = args.ScriptExecutor.Execute(new()
|
||||
{
|
||||
Args = args,
|
||||
Code = Language is ScriptLanguage.CSharp or ScriptLanguage.JavaScript ? Code : args.ReplaceVariables(Code),
|
||||
ScriptType = ScriptType.Flow,
|
||||
Language = Language
|
||||
});
|
||||
|
||||
if (result.Failed(out var error))
|
||||
{
|
||||
args.FailureReason = error;
|
||||
args.Logger?.ELog(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (result.Value > Outputs)
|
||||
{
|
||||
args.FailureReason = "Unexpected output: " + result.Value;
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return result.Value;
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
namespace FileFlows.BasicNodes.Scripting;
|
||||
|
||||
/// <summary>
|
||||
/// Flow element that executes a SH script
|
||||
/// </summary>
|
||||
public class ShScript : Node
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override int Inputs => 1;
|
||||
/// <inheritdoc />
|
||||
public override int Outputs => 2;
|
||||
/// <inheritdoc />
|
||||
public override FlowElementType Type => FlowElementType.Process;
|
||||
/// <inheritdoc />
|
||||
public override string Icon => "svg:sh";
|
||||
/// <inheritdoc />
|
||||
public override bool FailureNode => true;
|
||||
/// <inheritdoc />
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/sh-script";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the code to execute
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DefaultValue(@"# This is a template shell script
|
||||
|
||||
# Replace {file.FullName} and {file.Orig.FullName} with actual values
|
||||
WorkingFile=""{file.FullName}""
|
||||
OriginalFile=""{file.Orig.FullName}""
|
||||
|
||||
# Example commands using the variables
|
||||
echo ""Working on file: $WorkingFile""
|
||||
echo ""Original file location: $OriginalFile""
|
||||
|
||||
# Add your actual shell commands below
|
||||
# Example: Copy the working file to a backup location
|
||||
# cp ""$WorkingFile"" ""/path/to/backup/$(basename \""$WorkingFile\"")""
|
||||
|
||||
# Set the exit code to 0
|
||||
exit 0
|
||||
")]
|
||||
[Code(1, "sh")]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Code))
|
||||
{
|
||||
args.FailureReason = "No code specified in SH script";
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1; // no code, flow cannot continue doesn't know what to do
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
args.FailureReason = "Cannot run a SH script on a Windows system";
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var shFile = System.IO.Path.Combine(args.TempPath, Guid.NewGuid() + ".sh");
|
||||
|
||||
try
|
||||
{
|
||||
var code = args.ReplaceVariables(Code);
|
||||
args.Logger?.ILog("Executing code: \n" + code);
|
||||
System.IO.File.WriteAllText(shFile, code);
|
||||
args.Logger?.ILog($"Temporary SH file created: {shFile}");
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "/bin/bash",
|
||||
Arguments = $"\"{shFile}\"",
|
||||
WorkingDirectory = args.TempPath,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo };
|
||||
process.Start();
|
||||
|
||||
string standardOutput = process.StandardOutput.ReadToEnd();
|
||||
string standardError = process.StandardError.ReadToEnd();
|
||||
|
||||
process.WaitForExit();
|
||||
int exitCode = process.ExitCode;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(standardOutput))
|
||||
args.Logger?.ILog($"Standard Output:\n{standardOutput}");
|
||||
if (!string.IsNullOrWhiteSpace(standardError))
|
||||
args.Logger?.WLog($"Standard Error:\n{standardError}");
|
||||
args.Logger?.ILog($"Exit Code: {exitCode}");
|
||||
|
||||
return exitCode == 0 ? 1 : 2;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.FailureReason = "Failed executing SH script: " + ex.Message;
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(shFile))
|
||||
{
|
||||
System.IO.File.Delete(shFile);
|
||||
args.Logger?.ILog($"Temporary SH file deleted: {shFile}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.Logger?.WLog($"Failed to delete temporary SH file: {shFile}. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
BasicNodes/Scripting/ShellScript.cs
Normal file
51
BasicNodes/Scripting/ShellScript.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BasicNodes.Scripting;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
namespace FileFlows.BasicNodes.Scripting;
|
||||
|
||||
/// <summary>
|
||||
/// Flow element that executes a Shell script
|
||||
/// </summary>
|
||||
public class ShellScript : ScriptBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Icon => "svg:sh";
|
||||
/// <inheritdoc />
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/shell-script";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ScriptLanguage Language => ScriptLanguage.Shell;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the code to execute
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DefaultValue(@"
|
||||
# A Shell script can communicate with FileFlows to determine which output to call next by using exit codes.
|
||||
# Exit codes are zero-based, so:
|
||||
# Exit Code 0 corresponds to Output 1
|
||||
# Exit Code 1 corresponds to Output 2
|
||||
# Exit Code 2 corresponds to Output 3
|
||||
# and so on. Exit codes outside the defined range will be treated as a failure output.
|
||||
|
||||
# Replace {file.FullName} and {file.Orig.FullName} with actual values
|
||||
WorkingFile=""{file.FullName}""
|
||||
OriginalFile=""{file.Orig.FullName}""
|
||||
|
||||
# Example commands using the variables
|
||||
echo ""Working on file: $WorkingFile""
|
||||
echo ""Original file location: $OriginalFile""
|
||||
|
||||
# Add your actual shell commands below
|
||||
# Example: Copy the working file to a backup location
|
||||
# cp ""$WorkingFile"" ""/path/to/backup/$(basename \""$WorkingFile\"")""
|
||||
|
||||
# Set the exit code to 0
|
||||
exit 0
|
||||
")]
|
||||
[Code(2, "sh")]
|
||||
public override string Code { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user