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