From 5ee56905cefdbc892646750900b528de7b7c5411 Mon Sep 17 00:00:00 2001 From: Cezary Piatek Date: Tue, 20 Jan 2026 18:13:41 +0100 Subject: [PATCH 1/3] Fix FilePicker crash when value is set to non-existent path --- src/ScriptRunner/ScriptRunner.GUI/Views/FilePicker.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptRunner/ScriptRunner.GUI/Views/FilePicker.axaml.cs b/src/ScriptRunner/ScriptRunner.GUI/Views/FilePicker.axaml.cs index 97ee4f6..7f164d3 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Views/FilePicker.axaml.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Views/FilePicker.axaml.cs @@ -39,7 +39,7 @@ private async void ChangeFileClick(object? sender, RoutedEventArgs e) var sourceWindow = (sender as Control)?.GetVisualRoot() as Window ?? desktop.MainWindow; var dialog = new OpenFileDialog(); - if (string.IsNullOrWhiteSpace(FilePath) == false && Path.GetDirectoryName(FilePath) is { } dir) + if (string.IsNullOrWhiteSpace(FilePath) == false && Path.GetDirectoryName(FilePath) is { } dir && Directory.Exists(dir)) { dialog.Directory = dir; dialog.InitialFileName = Path.GetFileName(FilePath); From a08810b67526a231586515f752d614dc9497f2af Mon Sep 17 00:00:00 2001 From: Cezary Piatek Date: Tue, 20 Jan 2026 18:44:21 +0100 Subject: [PATCH 2/3] Add fallback to existing values if defaults are not defined --- README.md | 2 + schema/v1/ScriptRunnerSchema.json | 4 ++ .../ScriptConfigs/ScriptConfig.cs | 3 +- .../ScriptReader/ScriptConfigReader.cs | 12 ++++- .../Scripts/TextInputScript.json | 51 +++++++++++++++++++ .../ViewModels/MainWindowViewModel.cs | 28 ++++++++++ 6 files changed, 98 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f83d7a..2a4cf3f 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,8 @@ Bundle common parameter combinations into named presets so teammates can run fre `fallbackToDefault` fills in any missing parameters from the automatically generated `` set. +`fallbackToExisting` (optional, works with `fallbackToDefault`) prevents clearing field values when switching to this argument set if the set doesn't define a value for a parameter (or has an empty value). When set to `true`, existing values in the form are preserved instead of being cleared or overwritten with empty defaults. This is useful when you want to change only specific parameters while keeping others unchanged. + ## Ready-to-run examples The repository ships with `examples/ScriptRunnerExamples.json`, a manifest that contains four minimal actions showcasing how to wrap PowerShell, Python, Bash (via WSL), and .NET console apps. Import that file from **Settings ➜ Config sources** and you will get the following definitions (interactive inputs and troubleshooting rules are omitted here for brevity): diff --git a/schema/v1/ScriptRunnerSchema.json b/schema/v1/ScriptRunnerSchema.json index 04a86eb..ffa15e4 100644 --- a/schema/v1/ScriptRunnerSchema.json +++ b/schema/v1/ScriptRunnerSchema.json @@ -82,6 +82,10 @@ "type":"boolean", "description":"When true any missing parameter values inherit from the auto-generated default set." }, + "fallbackToExisting":{ + "type":"boolean", + "description":"When true preserves current field values when switching to this set if the set doesn't define a value or has an empty value for a parameter. Works with fallbackToDefault." + }, "arguments":{ "type":"object", "description":"Map of parameter name to the value that should be pre-filled when this preset is selected.", diff --git a/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs b/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs index a698562..241fb0b 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs @@ -1,4 +1,4 @@ -using Avalonia.Controls.Documents; +using Avalonia.Controls.Documents; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -53,6 +53,7 @@ public class ArgumentSet { public string Description { get; set; } public bool FallbackToDefault { get; set; } + public bool FallbackToExisting { get; set; } public Dictionary Arguments { get; set; } = new(); } diff --git a/src/ScriptRunner/ScriptRunner.GUI/ScriptReader/ScriptConfigReader.cs b/src/ScriptRunner/ScriptRunner.GUI/ScriptReader/ScriptConfigReader.cs index b802bb3..e238db0 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ScriptReader/ScriptConfigReader.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ScriptReader/ScriptConfigReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data.SqlTypes; using System.Diagnostics.CodeAnalysis; @@ -181,6 +181,11 @@ string ResolveAbsolutePath(string path) { if (set.Arguments.ContainsKey(key) == false) { + // If fallbackToExisting is true, only set the value if the default value exists (is not null or empty) + if (set.FallbackToExisting && string.IsNullOrEmpty(val)) + { + continue; + } set.Arguments[key] = val; } } @@ -348,6 +353,11 @@ string ResolveAbsolutePath(string path) { if (set.Arguments.ContainsKey(key) == false) { + // If fallbackToExisting is true, only set the value if the default value exists (is not null or empty) + if (set.FallbackToExisting && string.IsNullOrEmpty(val)) + { + continue; + } set.Arguments[key] = val; } } diff --git a/src/ScriptRunner/ScriptRunner.GUI/Scripts/TextInputScript.json b/src/ScriptRunner/ScriptRunner.GUI/Scripts/TextInputScript.json index a67b688..ff8bc84 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Scripts/TextInputScript.json +++ b/src/ScriptRunner/ScriptRunner.GUI/Scripts/TextInputScript.json @@ -27,6 +27,57 @@ } ] }, + { + "name": "FallbackToExisting Demo", + "description": "Demonstrates the fallbackToExisting feature", + "command": "echo Server: {server}, Port: {port}, Username: {username}", + "params": [ + { + "name": "server", + "description": "Server address", + "prompt": "text", + "default": "localhost" + }, + { + "name": "port", + "description": "Port number", + "prompt": "text", + "default": "" + }, + { + "name": "username", + "description": "Username", + "prompt": "text", + "default": "" + } + ], + "predefinedArgumentSets": [ + { + "description": "Production Server Only", + "fallbackToDefault": true, + "fallbackToExisting": true, + "arguments": { + "server": "prod.example.com" + } + }, + { + "description": "Dev Server (clears other fields)", + "fallbackToDefault": true, + "arguments": { + "server": "dev.example.com" + } + }, + { + "description": "Full Production Config", + "fallbackToDefault": true, + "arguments": { + "server": "prod.example.com", + "port": "443", + "username": "admin" + } + } + ] + }, { "name": "AllControlsTest", "description": "Demo of all available controls", diff --git a/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs b/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs index 0e13376..e27a4e4 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs @@ -794,6 +794,34 @@ public ArgumentSet? SelectedArgumentSet } } + // Handle fallbackToExisting: preserve current values when new set has empty values + if (_selectedArgumentSet.FallbackToExisting && _controlRecords != null) + { + // Harvest current parameter values from UI controls + var currentValues = new Dictionary(); + foreach (var controlRecord in _controlRecords) + { + var controlValue = controlRecord.GetFormattedValue()?.Trim(); + if (!string.IsNullOrEmpty(controlValue)) + { + currentValues[controlRecord.Name] = controlValue; + } + } + + // Merge: use new set's values if non-empty, otherwise preserve current values + arguments = new Dictionary(arguments); + foreach (var param in selectedAction.Params) + { + // If the new set doesn't have this parameter or has an empty value + if ((!arguments.ContainsKey(param.Name) || string.IsNullOrEmpty(arguments[param.Name])) + && currentValues.ContainsKey(param.Name)) + { + // Preserve the current value + arguments[param.Name] = currentValues[param.Name]; + } + } + } + RenderParameterForm(selectedAction, arguments); } } From f35532531c4d40c3fbbcd95faec942bc30bcda1c Mon Sep 17 00:00:00 2001 From: Cezary Piatek Date: Tue, 20 Jan 2026 18:55:36 +0100 Subject: [PATCH 3/3] Store FileContent control state in AppData directory --- .../ScriptRunner.GUI/Parameters/FileContent.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/FileContent.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/FileContent.cs index aa72fbe..dde74c1 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Parameters/FileContent.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/FileContent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Security.Cryptography; using System.Text; @@ -18,7 +18,7 @@ public FileContent(string extension, bool useWslPath) { _extension = extension; _useWslPath = useWslPath; - FileName = Path.GetTempFileName() + "." + extension; + FileName = GetFileContentStoragePath("temp." + extension); } public string GetFormattedValue() @@ -30,12 +30,21 @@ public string GetFormattedValue() _ => ((TextBox)Control).Text }; var hash = string.IsNullOrWhiteSpace(fileContent)? "EMPTY" : ComputeSHA256(fileContent).Substring(0,10); - FileName = Path.Combine(Path.GetTempPath(), hash + "." + _extension); + FileName = GetFileContentStoragePath(hash + "." + _extension); File.WriteAllText(FileName, fileContent, Encoding.UTF8); return _useWslPath ? WslPathConverter.ConvertToWslPath(FileName) : FileName; } + private static string GetFileContentStoragePath(string fileName) + { + var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ScriptRunner", "FileContentStorage"); + if (Directory.Exists(appDataPath) == false) + { + Directory.CreateDirectory(appDataPath); + } + return Path.Combine(appDataPath, fileName); + } static string ComputeSHA256(string input) {