From e15ec86d772c7d7b9c6329bc413c78f360dbe5a3 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 5 Jul 2026 00:21:42 +0200
Subject: [PATCH 1/5] Feature: Migrate variable syntax to {{varname}}
---
Source/GlobalAssemblyInfo.cs | 4 +-
.../Resources/StaticStrings.resx | 2 +-
.../Resources/Strings.Designer.cs | 41 ++++---
.../Resources/Strings.resx | 15 +--
.../Network/IPScannerCustomCommand.cs | 6 +-
.../SettingsManager.cs | 26 +++++
.../ViewModels/IPScannerViewModel.cs | 8 +-
.../Views/CustomCommandChildWindow.xaml | 100 ++++++++++++++++--
Website/docs/application/ip-scanner.md | 18 ++--
Website/docs/changelog/next-release.md | 1 +
10 files changed, 175 insertions(+), 46 deletions(-)
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..a7846cdc80 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -3018,7 +3018,34 @@ 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 Deselect all.
///
@@ -5064,18 +5091,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..8c9295b8ce 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -477,6 +477,15 @@
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:
+
DHCP enabled
@@ -2244,12 +2253,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.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index dc3493591a..b012a703c9 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,31 @@ 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 = Regex.Replace(customCommand.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/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs
index 70c4685d07..05be99328b 100644
--- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs
@@ -581,14 +581,14 @@ 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 = Regex.Replace(info.FilePath, "\\{\\{hostname\\}\\}", hostname, RegexOptions.IgnoreCase);
+ info.FilePath = Regex.Replace(info.FilePath, "\\{\\{ipaddress\\}\\}", ipAddress, RegexOptions.IgnoreCase);
if (!string.IsNullOrEmpty(info.Arguments))
{
- info.Arguments = Regex.Replace(info.Arguments, "\\$\\$hostname\\$\\$", hostname,
+ info.Arguments = Regex.Replace(info.Arguments, "\\{\\{hostname\\}\\}", hostname,
RegexOptions.IgnoreCase);
- info.Arguments = Regex.Replace(info.Arguments, "\\$\\$ipaddress\\$\\$", ipAddress,
+ info.Arguments = Regex.Replace(info.Arguments, "\\{\\{ipaddress\\}\\}", ipAddress,
RegexOptions.IgnoreCase);
}
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/Website/docs/application/ip-scanner.md b/Website/docs/application/ip-scanner.md
index a9263f2a27..3ba6161ea6 100644
--- a/Website/docs/application/ip-scanner.md
+++ b/Website/docs/application/ip-scanner.md
@@ -208,18 +208,18 @@ Custom commands that can be executed with a right click on the selected result.
**Default:**
-| Name | File path | Arguments |
-| --------------------- | -------------- | ------------------------------------------------ |
-| Edge | `cmd.exe` | `/c start microsoft-edge:http://$$ipaddress$$/` |
-| Edge (https) | `cmd.exe` | `/c start microsoft-edge:https://$$ipaddress$$/` |
-| Windows Explorer (c$) | `explorer.exe` | `\\$$ipaddress$$\c$` |
+| Name | File path | Arguments |
+| --------------------- | -------------- | -------------------------------------------------- |
+| Edge | `cmd.exe` | `/c start microsoft-edge:http://{{IPAddress}}/` |
+| Edge (https) | `cmd.exe` | `/c start microsoft-edge:https://{{IPAddress}}/` |
+| Windows Explorer (c$) | `explorer.exe` | `\\{{IPAddress}}\c$` |
In the arguments you can use the following placeholders:
-| Placeholder | Description |
-| --------------- | ----------- |
-| `$$ipaddress$$` | IP address |
-| `$$hostname$$` | Hostname |
+| Placeholder | Description |
+| ---------------- | ----------- |
+| `{{IPAddress}}` | IP address |
+| `{{Hostname}}` | Hostname |
:::note
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 29aede1745..40478ed0fb 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -29,6 +29,7 @@ Release date: **xx.xx.2025**
- **ARP Table** has been renamed to **[Neighbor Table](../application/neighbor-table.md)**. The application list entry is automatically migrated on first launch. Other view settings (auto-refresh interval, export file type/path) reset to their defaults. [#3403](https://github.com/BornToBeRoot/NETworkManager/pull/3403)
- **IP Scanner** export: The `ARPMACAddress` and `ARPVendor` columns have been removed from CSV, XML and JSON exports. Use `MACAddress` and `Vendor` instead, which contain the same value (ARP/NDP preferred, NetBIOS as fallback). [#3403](https://github.com/BornToBeRoot/NETworkManager/pull/3403)
+- **IP Scanner** custom commands: The placeholder syntax has changed from `$$ipaddress$$` / `$$hostname$$` to `{{IPAddress}}` / `{{Hostname}}`. Existing custom commands are automatically migrated on first launch.
## What's new?
From 15a29eefd5915713f0877871de16c31e3331c271 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 5 Jul 2026 02:13:47 +0200
Subject: [PATCH 2/5] Feature: Add placeholder for host
---
.../Resources/Strings.Designer.cs | 9 ++++
.../Resources/Strings.resx | 3 ++
.../Application/PowerShell.cs | 13 +++--
.../Application/PuTTY.cs | 13 +++--
.../Application/WebConsole.cs | 3 +-
.../NETworkManager.Profiles.csproj | 1 +
.../PlaceholderHelper.cs | 53 +++++++++++++++++++
.../WebConsoleProfileUrlValidator.cs | 26 +++++++++
.../ViewModels/IPScannerViewModel.cs | 13 +++--
.../ViewModels/PowerShellHostViewModel.cs | 3 +-
.../ViewModels/PuTTYHostViewModel.cs | 3 +-
.../Views/ProfileChildWindow.xaml | 23 +++++++-
Website/docs/application/powershell.md | 24 +++++++++
Website/docs/application/putty.md | 8 +++
Website/docs/application/web-console.md | 6 +++
Website/docs/changelog/next-release.md | 4 ++
16 files changed, 184 insertions(+), 21 deletions(-)
create mode 100644 Source/NETworkManager.Utilities/PlaceholderHelper.cs
create mode 100644 Source/NETworkManager.Validators/WebConsoleProfileUrlValidator.cs
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index a7846cdc80..1431ed5e39 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -3046,6 +3046,15 @@ public static string HelpMessage_CustomCommandArguments {
}
}
+ ///
+ /// Looks up a localized string similar to {{Host}} can be used to insert the profile's host..
+ ///
+ public static string HelpMessage_ProfileHostPlaceholder {
+ get {
+ return ResourceManager.GetString("HelpMessage_ProfileHostPlaceholder", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Deselect all.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 8c9295b8ce..294a8a3da8 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -486,6 +486,9 @@
Arguments passed to the file or executable. The following placeholders can be used:
+
+ {{Host}} can be used to insert the profile's host.
+
DHCP enabled
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.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..5c3f59a441
--- /dev/null
+++ b/Source/NETworkManager.Validators/WebConsoleProfileUrlValidator.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Globalization;
+using System.Text.RegularExpressions;
+using System.Windows.Controls;
+using NETworkManager.Localization.Resources;
+
+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 = Regex.Replace(value as string ?? "", "\\{\\{host\\}\\}", "placeholder-host",
+ RegexOptions.IgnoreCase);
+
+ 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 05be99328b..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/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/ProfileChildWindow.xaml b/Source/NETworkManager/Views/ProfileChildWindow.xaml
index 90da9df9f3..2ecdf7bbe1 100644
--- a/Source/NETworkManager/Views/ProfileChildWindow.xaml
+++ b/Source/NETworkManager/Views/ProfileChildWindow.xaml
@@ -2806,6 +2806,13 @@
Text="{Binding PowerShell_AdditionalCommandLine, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding PowerShell_OverrideAdditionalCommandLine}"
Style="{StaticResource DefaultTextBox}" />
+
+
+
+
+
+
+
+
+
+
@@ -3248,11 +3262,18 @@
UpdateSourceTrigger="PropertyChanged">
-
+
+
+
+
+
+
diff --git a/Website/docs/application/powershell.md b/Website/docs/application/powershell.md
index b244736e0c..bd8f161e87 100644
--- a/Website/docs/application/powershell.md
+++ b/Website/docs/application/powershell.md
@@ -130,6 +130,12 @@ Additional command line arguments to pass to the PowerShell console when it is s
**Default:** [`Settings > Additional command line`](#additional-command-line-3)
+:::note
+
+`{{Host}}` can be used to insert the [Host](#host) entered above.
+
+:::
+
### Execution policy
Execution policy of the PowerShell console when it is started.
@@ -219,6 +225,12 @@ Additional command line arguments to pass to the PowerShell console when it is s
**Default:** `Empty`
+:::note
+
+`{{Host}}` can be used to insert the profile's host.
+
+:::
+
### Execution policy
Execution policy of the PowerShell console when it is started.
@@ -263,6 +275,12 @@ Additional command line arguments to pass to the PowerShell console when it is s
**Default:** `Empty`
+:::note
+
+`{{Host}}` can be used to insert the profile's host.
+
+:::
+
### Execution policy
Execution policy of the PowerShell console when it is started.
@@ -325,6 +343,12 @@ Default additional command line arguments to pass to the PowerShell console when
**Default:** `Empty`
+:::note
+
+`{{Host}}` can be used to insert the host.
+
+:::
+
### Execution policy
Default execution policy of the PowerShell console when it is started.
diff --git a/Website/docs/application/putty.md b/Website/docs/application/putty.md
index 385460e6b8..57e95245f0 100644
--- a/Website/docs/application/putty.md
+++ b/Website/docs/application/putty.md
@@ -208,6 +208,8 @@ Additional command line parameters to use for the connection which are appended.
Additional command line parameters to use for the connection. For more information about the available parameters, see the [PuTTY documentation](https://the.earth.li/~sgtatham/putty/latest/htmldoc/Chapter3.html).
+`{{Host}}` can be used to insert the [Host](#host) entered above (not resolved if [Mode](#mode) is set to `Serial`, since there is no host in that mode).
+
:::
## Profile
@@ -477,6 +479,8 @@ Additional command line parameters to use for the connection which are appended.
Additional command line parameters to use for the connection. For more information about the available parameters, see the [PuTTY documentation](https://the.earth.li/~sgtatham/putty/latest/htmldoc/Chapter3.html).
+`{{Host}}` can be used to insert the profile's host.
+
:::
## Group
@@ -609,6 +613,8 @@ Additional command line parameters to use for the connection which are appended.
Additional command line parameters to use for the connection. For more information about the available parameters, see the [PuTTY documentation](https://the.earth.li/~sgtatham/putty/latest/htmldoc/Chapter3.html).
+`{{Host}}` can be used to insert the profile's host.
+
:::
## Settings
@@ -775,6 +781,8 @@ Additional command line parameters to use for the connection which are appended.
Additional command line parameters to use for the connection. For more information about the available parameters, see the [PuTTY documentation](https://the.earth.li/~sgtatham/putty/latest/htmldoc/Chapter3.html).
+`{{Host}}` can be used to insert the host.
+
:::
### SSH port
diff --git a/Website/docs/application/web-console.md b/Website/docs/application/web-console.md
index 38d2a70d70..03dab959f3 100644
--- a/Website/docs/application/web-console.md
+++ b/Website/docs/application/web-console.md
@@ -52,6 +52,12 @@ URL of the website to display in the web console.
**Example:** `https://pihole.borntoberoot.net/admin/`
+:::note
+
+`{{Host}}` can be used to insert the profile's host (e.g. `https://{{Host}}/`).
+
+:::
+
## Settings
### Show address bar
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 40478ed0fb..5ac3ab8b8c 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -90,6 +90,10 @@ Release date: **xx.xx.2025**
- MAC address resolution now uses ARP (IPv4) or NDP (IPv6) from the neighbor cache, with NetBIOS as fallback. The detail panel shows a single **MAC Address** section instead of separate ARP and NetBIOS entries. [#3403](https://github.com/BornToBeRoot/NETworkManager/pull/3403)
+**Profiles**
+
+- **WebConsole** URL, **PowerShell** additional command line, and **PuTTY** additional command line now support a `{{Host}}` placeholder (e.g. `https://{{Host}}/`) that is resolved to the profile's host each time a connection is established, so it always reflects the current host even after later edits.
+
**Dashboard**
- Added a **Refresh** button (with animated feedback) to the **Network Connection**, **IP Geolocation**, and **DNS Resolver** widgets. The global reload button in the Status Window has been removed, as each widget now has its own. [#3447](https://github.com/BornToBeRoot/NETworkManager/pull/3447)
From 1d51b645c937c224839bae9b95b9a65cc8716662 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 5 Jul 2026 03:03:52 +0200
Subject: [PATCH 3/5] Feature: Add variable to profiles
---
.../Resources/Strings.Designer.cs | 2 +-
.../Resources/Strings.resx | 2 +-
Source/NETworkManager.Profiles/ProfileInfo.cs | 2 +-
.../SettingsManager.cs | 9 +-
.../ImportProfilesResultViewModel.cs | 4 +
.../Views/GroupChildWindow.xaml | 92 +++++++++++-
.../Views/PowerShellConnectChildWindow.xaml | 46 ++++++
.../Views/PowerShellSettingsView.xaml | 50 ++++++-
.../Views/ProfileChildWindow.xaml | 138 ++++++++++++++++--
.../Views/PuTTYConnectChildWindow.xaml | 45 ++++++
.../Views/PuTTYSettingsView.xaml | 50 ++++++-
Website/docs/application/ip-scanner.md | 6 +-
Website/docs/changelog/next-release.md | 3 +-
13 files changed, 423 insertions(+), 26 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 1431ed5e39..8e687be8a2 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -3047,7 +3047,7 @@ public static string HelpMessage_CustomCommandArguments {
}
///
- /// Looks up a localized string similar to {{Host}} can be used to insert the profile's host..
+ /// Looks up a localized string similar to The following placeholder can be used:.
///
public static string HelpMessage_ProfileHostPlaceholder {
get {
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 294a8a3da8..de5c75f684 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -487,7 +487,7 @@
Arguments passed to the file or executable. The following placeholders can be used:
- {{Host}} can be used to insert the profile's host.
+ The following placeholder can be used:
DHCP enabled
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 b012a703c9..4bcc92ad9f 100644
--- a/Source/NETworkManager.Settings/SettingsManager.cs
+++ b/Source/NETworkManager.Settings/SettingsManager.cs
@@ -705,8 +705,13 @@ private static void UpgradeToLatest(Version version)
foreach (var customCommand in Current.IPScanner_CustomCommands)
{
- var filePath = Regex.Replace(customCommand.FilePath, "\\$\\$ipaddress\\$\\$", "{{IPAddress}}", RegexOptions.IgnoreCase);
- filePath = Regex.Replace(filePath, "\\$\\$hostname\\$\\$", "{{Hostname}}", RegexOptions.IgnoreCase);
+ 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;
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/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" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3268,11 +3344,49 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+