diff --git a/Source/GlobalAssemblyInfo.cs b/Source/GlobalAssemblyInfo.cs index 4e5fde9f3b..e0c2cd31d9 100644 --- a/Source/GlobalAssemblyInfo.cs +++ b/Source/GlobalAssemblyInfo.cs @@ -6,5 +6,5 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2026.5.30.0")] -[assembly: AssemblyFileVersion("2026.5.30.0")] +[assembly: AssemblyVersion("2026.7.4.0")] +[assembly: AssemblyFileVersion("2026.7.4.0")] diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.resx b/Source/NETworkManager.Localization/Resources/StaticStrings.resx index e9ca7ea40d..897bf115a8 100644 --- a/Source/NETworkManager.Localization/Resources/StaticStrings.resx +++ b/Source/NETworkManager.Localization/Resources/StaticStrings.resx @@ -277,7 +277,7 @@ iexplore.exe - https://$$HOSTNAME$$/ + https://{{Hostname}}/ Internet Explorer diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs index fca07f29c8..8e687be8a2 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs +++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs @@ -3018,7 +3018,43 @@ public static string Description { return ResourceManager.GetString("Description", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Placeholder. + /// + public static string Placeholder { + get { + return ResourceManager.GetString("Placeholder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Path to the file or executable to run. The following placeholders can be used:. + /// + public static string HelpMessage_CustomCommandFilePath { + get { + return ResourceManager.GetString("HelpMessage_CustomCommandFilePath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Arguments passed to the file or executable. The following placeholders can be used:. + /// + public static string HelpMessage_CustomCommandArguments { + get { + return ResourceManager.GetString("HelpMessage_CustomCommandArguments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The following placeholder can be used:. + /// + public static string HelpMessage_ProfileHostPlaceholder { + get { + return ResourceManager.GetString("HelpMessage_ProfileHostPlaceholder", resourceCulture); + } + } + /// /// Looks up a localized string similar to Deselect all. /// @@ -5064,18 +5100,6 @@ public static string HelpMessage_Credentials { } } - /// - /// Looks up a localized string similar to The following variables are available: - /// - ///$$ipaddress$$ --> IP adresse - ///$$hostname$$ --> Hostname. - /// - public static string HelpMessage_CustomCommandVariables { - get { - return ResourceManager.GetString("HelpMessage_CustomCommandVariables", resourceCulture); - } - } - /// /// Looks up a localized string similar to URL to a web service that can be reached via http or https and returns an IPv4 address e.g., "xx.xx.xx.xx" as response.. /// diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx index f3c9912eec..de5c75f684 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.resx +++ b/Source/NETworkManager.Localization/Resources/Strings.resx @@ -477,6 +477,18 @@ Description + + Placeholder + + + Path to the file or executable to run. The following placeholders can be used: + + + Arguments passed to the file or executable. The following placeholders can be used: + + + The following placeholder can be used: + DHCP enabled @@ -2244,12 +2256,6 @@ is disabled! The selected custom command will be deleted permanently. - - The following variables are available: - -$$ipaddress$$ --> IP adresse -$$hostname$$ --> Hostname - Restart diff --git a/Source/NETworkManager.Models/Network/IPScannerCustomCommand.cs b/Source/NETworkManager.Models/Network/IPScannerCustomCommand.cs index 85574bf1a4..7ab60cd08b 100644 --- a/Source/NETworkManager.Models/Network/IPScannerCustomCommand.cs +++ b/Source/NETworkManager.Models/Network/IPScannerCustomCommand.cs @@ -10,10 +10,10 @@ public static List GetDefaultList() { return new List { - new(Guid.NewGuid(), "Edge", "cmd.exe", @"/c start microsoft-edge:http://$$ipaddress$$/"), + new(Guid.NewGuid(), "Edge", "cmd.exe", @"/c start microsoft-edge:http://{{IPAddress}}/"), new(Guid.NewGuid(), "Edge (https)", "cmd.exe", - @"/c start microsoft-edge:https://$$ipaddress$$/"), - new(Guid.NewGuid(), "Windows Explorer (c$)", "explorer.exe", @"\\$$ipaddress$$\c$") + @"/c start microsoft-edge:https://{{IPAddress}}/"), + new(Guid.NewGuid(), "Windows Explorer (c$)", "explorer.exe", @"\\{{IPAddress}}\c$") }; } } \ No newline at end of file diff --git a/Source/NETworkManager.Profiles/Application/PowerShell.cs b/Source/NETworkManager.Profiles/Application/PowerShell.cs index ad9235a1bc..8121fd860c 100644 --- a/Source/NETworkManager.Profiles/Application/PowerShell.cs +++ b/Source/NETworkManager.Profiles/Application/PowerShell.cs @@ -1,5 +1,6 @@ using NETworkManager.Models.PowerShell; using NETworkManager.Settings; +using NETworkManager.Utilities; namespace NETworkManager.Profiles.Application; @@ -20,11 +21,13 @@ public static PowerShellSessionInfo CreateSessionInfo(ProfileInfo profile) : group.PowerShell_OverrideCommand ? group.PowerShell_Command : SettingsManager.Current.PowerShell_Command, - AdditionalCommandLine = profile.PowerShell_OverrideAdditionalCommandLine - ? profile.PowerShell_AdditionalCommandLine - : group.PowerShell_OverrideAdditionalCommandLine - ? group.PowerShell_AdditionalCommandLine - : SettingsManager.Current.PowerShell_AdditionalCommandLine, + AdditionalCommandLine = PlaceholderHelper.Resolve( + profile.PowerShell_OverrideAdditionalCommandLine + ? profile.PowerShell_AdditionalCommandLine + : group.PowerShell_OverrideAdditionalCommandLine + ? group.PowerShell_AdditionalCommandLine + : SettingsManager.Current.PowerShell_AdditionalCommandLine, + (PlaceholderHelper.Host, profile.Host)), ExecutionPolicy = profile.PowerShell_OverrideExecutionPolicy ? profile.PowerShell_ExecutionPolicy : group.PowerShell_OverrideExecutionPolicy diff --git a/Source/NETworkManager.Profiles/Application/PuTTY.cs b/Source/NETworkManager.Profiles/Application/PuTTY.cs index 454e701c8a..ff425ff576 100644 --- a/Source/NETworkManager.Profiles/Application/PuTTY.cs +++ b/Source/NETworkManager.Profiles/Application/PuTTY.cs @@ -1,5 +1,6 @@ using NETworkManager.Models.PuTTY; using NETworkManager.Settings; +using NETworkManager.Utilities; namespace NETworkManager.Profiles.Application; @@ -55,11 +56,13 @@ public static PuTTYSessionInfo CreateSessionInfo(ProfileInfo profile) : group.PuTTY_OverrideLogFileName ? group.PuTTY_LogFileName : SettingsManager.Current.PuTTY_LogFileName, - AdditionalCommandLine = profile.PuTTY_OverrideAdditionalCommandLine - ? profile.PuTTY_AdditionalCommandLine - : group.PuTTY_OverrideAdditionalCommandLine - ? group.PuTTY_AdditionalCommandLine - : SettingsManager.Current.PuTTY_AdditionalCommandLine + AdditionalCommandLine = PlaceholderHelper.Resolve( + profile.PuTTY_OverrideAdditionalCommandLine + ? profile.PuTTY_AdditionalCommandLine + : group.PuTTY_OverrideAdditionalCommandLine + ? group.PuTTY_AdditionalCommandLine + : SettingsManager.Current.PuTTY_AdditionalCommandLine, + (PlaceholderHelper.Host, profile.Host)) }; } } \ No newline at end of file diff --git a/Source/NETworkManager.Profiles/Application/WebConsole.cs b/Source/NETworkManager.Profiles/Application/WebConsole.cs index 2704183672..e1f990c49d 100644 --- a/Source/NETworkManager.Profiles/Application/WebConsole.cs +++ b/Source/NETworkManager.Profiles/Application/WebConsole.cs @@ -1,4 +1,5 @@ using NETworkManager.Models.WebConsole; +using NETworkManager.Utilities; namespace NETworkManager.Profiles.Application; @@ -8,7 +9,7 @@ public static WebConsoleSessionInfo CreateSessionInfo(ProfileInfo profileInfo) { var info = new WebConsoleSessionInfo { - Url = profileInfo.WebConsole_Url + Url = PlaceholderHelper.Resolve(profileInfo.WebConsole_Url, (PlaceholderHelper.Host, profileInfo.Host)) }; return info; diff --git a/Source/NETworkManager.Profiles/NETworkManager.Profiles.csproj b/Source/NETworkManager.Profiles/NETworkManager.Profiles.csproj index ecabab5fa7..5af1160527 100644 --- a/Source/NETworkManager.Profiles/NETworkManager.Profiles.csproj +++ b/Source/NETworkManager.Profiles/NETworkManager.Profiles.csproj @@ -27,5 +27,6 @@ + diff --git a/Source/NETworkManager.Profiles/ProfileInfo.cs b/Source/NETworkManager.Profiles/ProfileInfo.cs index 3b36201016..80cb4937a4 100644 --- a/Source/NETworkManager.Profiles/ProfileInfo.cs +++ b/Source/NETworkManager.Profiles/ProfileInfo.cs @@ -465,7 +465,7 @@ public ProfileInfo(ProfileInfo profile) public int TigerVNC_Port { get; set; } = GlobalStaticConfiguration.TigerVNC_DefaultVNCPort; public bool WebConsole_Enabled { get; set; } - public string WebConsole_Url { get; set; } + public string WebConsole_Url { get; set; } = "https://{{Host}}"; public bool SNMP_Enabled { get; set; } public bool SNMP_InheritHost { get; set; } = true; diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs index dc3493591a..4bcc92ad9f 100644 --- a/Source/NETworkManager.Settings/SettingsManager.cs +++ b/Source/NETworkManager.Settings/SettingsManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.RegularExpressions; using System.Xml.Serialization; namespace NETworkManager.Settings; @@ -698,6 +699,36 @@ private static void UpgradeToLatest(Version version) Current.General_ApplicationList[index] = neighborTableEntry; } + + // IP Scanner - Custom commands + Log.Info("Migrating IP Scanner custom command placeholder syntax from \"$$var$$\" to \"{{Var}}\"..."); + + foreach (var customCommand in Current.IPScanner_CustomCommands) + { + var filePath = customCommand.FilePath; + + if (!string.IsNullOrEmpty(filePath)) + { + filePath = Regex.Replace(filePath, "\\$\\$ipaddress\\$\\$", "{{IPAddress}}", RegexOptions.IgnoreCase); + filePath = Regex.Replace(filePath, "\\$\\$hostname\\$\\$", "{{Hostname}}", RegexOptions.IgnoreCase); + } + + var arguments = customCommand.Arguments; + + if (!string.IsNullOrEmpty(arguments)) + { + arguments = Regex.Replace(arguments, "\\$\\$ipaddress\\$\\$", "{{IPAddress}}", RegexOptions.IgnoreCase); + arguments = Regex.Replace(arguments, "\\$\\$hostname\\$\\$", "{{Hostname}}", RegexOptions.IgnoreCase); + } + + if (filePath == customCommand.FilePath && arguments == customCommand.Arguments) + continue; + + Log.Info($"Migrated custom command \"{customCommand.Name}\"..."); + + customCommand.FilePath = filePath; + customCommand.Arguments = arguments; + } } #endregion } diff --git a/Source/NETworkManager.Utilities/PlaceholderHelper.cs b/Source/NETworkManager.Utilities/PlaceholderHelper.cs new file mode 100644 index 0000000000..2e206f1c9e --- /dev/null +++ b/Source/NETworkManager.Utilities/PlaceholderHelper.cs @@ -0,0 +1,53 @@ +using System.Text.RegularExpressions; + +namespace NETworkManager.Utilities; + +/// +/// Helper class to resolve {{name}} placeholders in a string. +/// +public static class PlaceholderHelper +{ + /// + /// Placeholder name for the host (e.g. of a profile). + /// + public const string Host = "host"; + + /// + /// Placeholder name for a hostname. + /// + public const string Hostname = "hostname"; + + /// + /// Placeholder name for an IP address. + /// + public const string IPAddress = "ipaddress"; + + /// + /// Replaces a {{name}} placeholder (case-insensitive) with the given value. + /// + /// Input string that may contain the placeholder. + /// Name of the placeholder, without the surrounding braces (e.g. "hostname"). + /// Value to insert in place of the placeholder. + /// The input with the placeholder resolved. + public static string Replace(string input, string name, string value) + { + return string.IsNullOrEmpty(input) + ? input + : Regex.Replace(input, $"\\{{\\{{{Regex.Escape(name)}\\}}\\}}", value ?? string.Empty, + RegexOptions.IgnoreCase); + } + + /// + /// Replaces multiple {{name}} placeholders (case-insensitive) with the given values. + /// + /// Input string that may contain the placeholders. + /// Placeholder name/value pairs to resolve, e.g. (PlaceholderHelper.Host, "10.0.0.1"). + /// The input with all placeholders resolved. + public static string Resolve(string input, params (string name, string value)[] placeholders) + { + foreach (var (name, value) in placeholders) + input = Replace(input, name, value); + + return input; + } +} diff --git a/Source/NETworkManager.Validators/WebConsoleProfileUrlValidator.cs b/Source/NETworkManager.Validators/WebConsoleProfileUrlValidator.cs new file mode 100644 index 0000000000..c60fe56330 --- /dev/null +++ b/Source/NETworkManager.Validators/WebConsoleProfileUrlValidator.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Windows.Controls; +using NETworkManager.Localization.Resources; +using NETworkManager.Utilities; + +namespace NETworkManager.Validators; + +/// +/// Validates the WebConsole URL field on a profile, which may contain the {{Host}} placeholder. +/// +public class WebConsoleProfileUrlValidator : ValidationRule +{ + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + // Substitute the {{Host}} placeholder with a dummy host so the structural + // URL validation (scheme, format) still applies, without rejecting the placeholder itself. + var input = PlaceholderHelper.Replace(value as string ?? "", PlaceholderHelper.Host, "placeholder-host"); + + return Uri.TryCreate(input, UriKind.Absolute, out var uriResult) && + (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps) + ? ValidationResult.ValidResult + : new ValidationResult(false, Strings.EnterValidWebsiteUri); + } +} diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index 70c4685d07..c5321a0d50 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -20,7 +20,6 @@ using System.Linq; using System.Net; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -581,15 +580,15 @@ private async Task CustomCommand(object guid) var hostname = !string.IsNullOrEmpty(SelectedResult.Hostname) ? SelectedResult.Hostname.TrimEnd('.') : ""; var ipAddress = SelectedResult.PingInfo.IPAddress.ToString(); - info.FilePath = Regex.Replace(info.FilePath, "\\$\\$hostname\\$\\$", hostname, RegexOptions.IgnoreCase); - info.FilePath = Regex.Replace(info.FilePath, "\\$\\$ipaddress\\$\\$", ipAddress, RegexOptions.IgnoreCase); + info.FilePath = PlaceholderHelper.Resolve(info.FilePath, + (PlaceholderHelper.Hostname, hostname), + (PlaceholderHelper.IPAddress, ipAddress)); if (!string.IsNullOrEmpty(info.Arguments)) { - info.Arguments = Regex.Replace(info.Arguments, "\\$\\$hostname\\$\\$", hostname, - RegexOptions.IgnoreCase); - info.Arguments = Regex.Replace(info.Arguments, "\\$\\$ipaddress\\$\\$", ipAddress, - RegexOptions.IgnoreCase); + info.Arguments = PlaceholderHelper.Resolve(info.Arguments, + (PlaceholderHelper.Hostname, hostname), + (PlaceholderHelper.IPAddress, ipAddress)); } try diff --git a/Source/NETworkManager/ViewModels/ImportProfilesResultViewModel.cs b/Source/NETworkManager/ViewModels/ImportProfilesResultViewModel.cs index 395603eb60..b99b0dc755 100644 --- a/Source/NETworkManager/ViewModels/ImportProfilesResultViewModel.cs +++ b/Source/NETworkManager/ViewModels/ImportProfilesResultViewModel.cs @@ -205,6 +205,7 @@ private static ObservableCollection BuildApplicatio new ImportApplicationToggleItem(ProfileName.PowerShell, true), new ImportApplicationToggleItem(ProfileName.PuTTY), new ImportApplicationToggleItem(ProfileName.TigerVNC), + new ImportApplicationToggleItem(ProfileName.WebConsole), new ImportApplicationToggleItem(ProfileName.SNMP), new ImportApplicationToggleItem(ProfileName.Whois), new ImportApplicationToggleItem(ProfileName.IPGeolocation) @@ -251,6 +252,9 @@ private static void ApplyToolDefaults(ProfileInfo profile, ProfileName tool) profile.TigerVNC_Enabled = true; profile.TigerVNC_Host = profile.Host; break; + case ProfileName.WebConsole: + profile.WebConsole_Enabled = true; + break; case ProfileName.SNMP: profile.SNMP_Enabled = true; profile.SNMP_Host = profile.Host; diff --git a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs index 4ac3744aae..c41c4552d6 100644 --- a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs @@ -638,7 +638,8 @@ private Task Connect(string host = null) EnableRemoteConsole = instance.EnableRemoteConsole, Host = instance.Host, Command = instance.Command, - AdditionalCommandLine = instance.AdditionalCommandLine, + AdditionalCommandLine = PlaceholderHelper.Resolve(instance.AdditionalCommandLine, + (PlaceholderHelper.Host, instance.Host)), ExecutionPolicy = instance.ExecutionPolicy }; diff --git a/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs b/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs index 7745f8c34f..1acf9bfc63 100644 --- a/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs @@ -574,7 +574,8 @@ private Task Connect(string host = null) LogMode = SettingsManager.Current.PuTTY_LogMode, LogFileName = SettingsManager.Current.PuTTY_LogFileName, LogPath = PuTTYProfile.LogPath, - AdditionalCommandLine = instance.AdditionalCommandLine + AdditionalCommandLine = PlaceholderHelper.Resolve(instance.AdditionalCommandLine, + (PlaceholderHelper.Host, instance.ConnectionMode == ConnectionMode.Serial ? null : instance.Host)) }; // Add to history diff --git a/Source/NETworkManager/Views/CustomCommandChildWindow.xaml b/Source/NETworkManager/Views/CustomCommandChildWindow.xaml index 03ef60c940..4b1b23228d 100644 --- a/Source/NETworkManager/Views/CustomCommandChildWindow.xaml +++ b/Source/NETworkManager/Views/CustomCommandChildWindow.xaml @@ -87,11 +87,53 @@ - - + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + diff --git a/Source/NETworkManager/Views/GroupChildWindow.xaml b/Source/NETworkManager/Views/GroupChildWindow.xaml index 0fc75ff188..38676ab0b2 100644 --- a/Source/NETworkManager/Views/GroupChildWindow.xaml +++ b/Source/NETworkManager/Views/GroupChildWindow.xaml @@ -1428,6 +1428,51 @@ Text="{Binding PowerShell_AdditionalCommandLine, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding PowerShell_OverrideAdditionalCommandLine}" Style="{StaticResource DefaultTextBox}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Source/NETworkManager/Views/PowerShellConnectChildWindow.xaml b/Source/NETworkManager/Views/PowerShellConnectChildWindow.xaml index 40daf75d76..bc72bb2b29 100644 --- a/Source/NETworkManager/Views/PowerShellConnectChildWindow.xaml +++ b/Source/NETworkManager/Views/PowerShellConnectChildWindow.xaml @@ -34,6 +34,7 @@ + @@ -81,6 +82,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/NETworkManager/Views/PowerShellSettingsView.xaml b/Source/NETworkManager/Views/PowerShellSettingsView.xaml index 14409048e9..5d30cc99f3 100644 --- a/Source/NETworkManager/Views/PowerShellSettingsView.xaml +++ b/Source/NETworkManager/Views/PowerShellSettingsView.xaml @@ -49,8 +49,54 @@ HorizontalAlignment="Left" Style="{StaticResource DefaultTextBox}" Margin="0,0,0,10" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3248,11 +3338,56 @@ UpdateSourceTrigger="PropertyChanged"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/NETworkManager/Views/PuTTYConnectChildWindow.xaml b/Source/NETworkManager/Views/PuTTYConnectChildWindow.xaml index f0f89aee3b..9bde7d772c 100644 --- a/Source/NETworkManager/Views/PuTTYConnectChildWindow.xaml +++ b/Source/NETworkManager/Views/PuTTYConnectChildWindow.xaml @@ -214,6 +214,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +