diff --git a/BasicNodes/Scripting/BatScript.cs b/BasicNodes/Scripting/BatScript.cs
index 7a5de4fe..892c752b 100644
--- a/BasicNodes/Scripting/BatScript.cs
+++ b/BasicNodes/Scripting/BatScript.cs
@@ -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;
///
/// Flow element that executes a bat script
///
-public class BatScript : Node
+public class BatScript : ScriptBase
{
- ///
- public override int Inputs => 1;
- ///
- public override int Outputs => 2;
- ///
- public override FlowElementType Type => FlowElementType.Process;
///
public override string Icon => "svg:bat";
///
- public override bool FailureNode => true;
- ///
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/bat-script";
+ ///
+ protected override ScriptLanguage Language => ScriptLanguage.Batch;
///
/// Gets or sets the code to execute
///
[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; }
-
- ///
- 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; }
}
\ No newline at end of file
diff --git a/BasicNodes/Scripting/CSharpScript.cs b/BasicNodes/Scripting/CSharpScript.cs
new file mode 100644
index 00000000..7e7332a1
--- /dev/null
+++ b/BasicNodes/Scripting/CSharpScript.cs
@@ -0,0 +1,43 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using FileFlows.Plugin;
+using FileFlows.Plugin.Attributes;
+
+namespace BasicNodes.Scripting;
+
+///
+/// Flow element that executes a CSharp script
+///
+public class CSharpScript : ScriptBase
+{
+ ///
+ public override string Icon => "svg:cs";
+
+ ///
+ public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/charp-script";
+
+ ///
+ protected override ScriptLanguage Language => ScriptLanguage.CSharp;
+
+ ///
+ /// Gets or sets the code to execute
+ ///
+ [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; }
+}
diff --git a/BasicNodes/Scripting/PowerShellScript.cs b/BasicNodes/Scripting/PowerShellScript.cs
index 4de6724e..95f8d989 100644
--- a/BasicNodes/Scripting/PowerShellScript.cs
+++ b/BasicNodes/Scripting/PowerShellScript.cs
@@ -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;
///
/// Flow element that executes a PowerShell script
///
-public class PowerShellScript : Node
+public class PowerShellScript : ScriptBase
{
- ///
- public override int Inputs => 1;
- ///
- public override int Outputs => 2;
- ///
- public override FlowElementType Type => FlowElementType.Process;
///
public override string Icon => "svg:ps1";
///
- public override bool FailureNode => true;
- ///
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/powershell-script";
+
+ ///
+ protected override ScriptLanguage Language => ScriptLanguage.PowerShell;
///
/// Gets or sets the code to execute
///
[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; }
-
- ///
- 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; }
}
\ No newline at end of file
diff --git a/BasicNodes/Scripting/ScriptBase.cs b/BasicNodes/Scripting/ScriptBase.cs
new file mode 100644
index 00000000..d96132a3
--- /dev/null
+++ b/BasicNodes/Scripting/ScriptBase.cs
@@ -0,0 +1,70 @@
+using System.ComponentModel;
+using FileFlows.Plugin;
+using FileFlows.Plugin.Attributes;
+
+namespace BasicNodes.Scripting;
+
+///
+/// Base for a script
+///
+public abstract class ScriptBase : Node
+{
+ ///
+ public override int Inputs => 1;
+ ///
+ public override FlowElementType Type => FlowElementType.Process;
+ ///
+ public override bool FailureNode => true;
+
+ ///
+ /// Gets or sets the number of outputs
+ ///
+ [DefaultValue(1)]
+ [NumberInt(1)]
+ public new int Outputs { get; set; }
+
+ ///
+ /// Gets the language of this script
+ ///
+ protected abstract ScriptLanguage Language { get; }
+
+ ///
+ /// Gets or sets the code of the script
+ ///
+ public virtual string Code { get; set; }
+
+ ///
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/BasicNodes/Scripting/ShScript.cs b/BasicNodes/Scripting/ShScript.cs
deleted file mode 100644
index 8d67f786..00000000
--- a/BasicNodes/Scripting/ShScript.cs
+++ /dev/null
@@ -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;
-
-///
-/// Flow element that executes a SH script
-///
-public class ShScript : Node
-{
- ///
- public override int Inputs => 1;
- ///
- public override int Outputs => 2;
- ///
- public override FlowElementType Type => FlowElementType.Process;
- ///
- public override string Icon => "svg:sh";
- ///
- public override bool FailureNode => true;
- ///
- public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/sh-script";
-
- ///
- /// Gets or sets the code to execute
- ///
- [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; }
-
- ///
- 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}");
- }
- }
- }
-}
diff --git a/BasicNodes/Scripting/ShellScript.cs b/BasicNodes/Scripting/ShellScript.cs
new file mode 100644
index 00000000..37882d5c
--- /dev/null
+++ b/BasicNodes/Scripting/ShellScript.cs
@@ -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;
+
+///
+/// Flow element that executes a Shell script
+///
+public class ShellScript : ScriptBase
+{
+ ///
+ public override string Icon => "svg:sh";
+ ///
+ public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/shell-script";
+
+ ///
+ protected override ScriptLanguage Language => ScriptLanguage.Shell;
+
+ ///
+ /// Gets or sets the code to execute
+ ///
+ [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; }
+}
diff --git a/BasicNodes/i18n/en.json b/BasicNodes/i18n/en.json
index 7c4241f4..c0a231ef 100644
--- a/BasicNodes/i18n/en.json
+++ b/BasicNodes/i18n/en.json
@@ -25,19 +25,35 @@
"Label": "BAT Script",
"Description": "Allows you to execute a batch (.bat) script in a Windows environment.",
"Outputs": {
- "1": "Success - The batch script executed successfully with an exit code of 0.",
- "2": "Failure - The batch script execution failed or returned a non-zero exit code."
+ "1": "returned 1",
+ "2": "returned 2",
+ "3": "returned 3",
+ "4": "returned 4",
+ "5": "returned 5",
+ "6": "returned 6",
+ "7": "returned 7",
+ "8": "returned 8",
+ "9": "returned 9",
+ "10": "returned 10"
},
"Fields": {
"Code": "Code"
}
},
- "ShScript": {
- "Label": "SH Script",
+ "ShellScript": {
+ "Label": "Shell Script",
"Description": "Allows you to execute a shell (.sh) script in a Unix-like environment.",
"Outputs": {
- "1": "Success - The shell script executed successfully with an exit code of 0.",
- "2": "Failure - The shell script execution failed or returned a non-zero exit code."
+ "1": "returned 1",
+ "2": "returned 2",
+ "3": "returned 3",
+ "4": "returned 4",
+ "5": "returned 5",
+ "6": "returned 6",
+ "7": "returned 7",
+ "8": "returned 8",
+ "9": "returned 9",
+ "10": "returned 10"
},
"Fields": {
"Code": "Code"
@@ -47,14 +63,41 @@
"Label": "PowerShell Script",
"Description": "Allows you to execute a PowerShell (.ps1) script in a Windows environment.",
"Outputs": {
- "1": "Success - The PowerShell script executed successfully with an exit code of 0.",
- "2": "Failure - The PowerShell script execution failed or returned a non-zero exit code."
+ "1": "returned 1",
+ "2": "returned 2",
+ "3": "returned 3",
+ "4": "returned 4",
+ "5": "returned 5",
+ "6": "returned 6",
+ "7": "returned 7",
+ "8": "returned 8",
+ "9": "returned 9",
+ "10": "returned 10"
},
"Fields": {
"Code": "Code"
}
}
},
+ "CSharpScript": {
+ "Label": "Sea Sharp Script",
+ "Description": "Allows you to execute a C# code inside the Flow.",
+ "Outputs": {
+ "1": "returned 1",
+ "2": "returned 2",
+ "3": "returned 3",
+ "4": "returned 4",
+ "5": "returned 5",
+ "6": "returned 6",
+ "7": "returned 7",
+ "8": "returned 8",
+ "9": "returned 9",
+ "10": "returned 10"
+ },
+ "Fields": {
+ "Code": "Code"
+ }
+ },
"CopyFile": {
"Description": "Copies a file to the destination folder",
"Outputs": {
diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll
index e3982128..a8ca92c2 100644
Binary files a/FileFlows.Plugin.dll and b/FileFlows.Plugin.dll differ
diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb
index ef634a3e..11ffd344 100644
Binary files a/FileFlows.Plugin.pdb and b/FileFlows.Plugin.pdb differ