diff --git a/Source/NETworkManager.Controls/ComboBoxPasteBehavior.cs b/Source/NETworkManager.Controls/ComboBoxPasteBehavior.cs
new file mode 100644
index 0000000000..0e5598b159
--- /dev/null
+++ b/Source/NETworkManager.Controls/ComboBoxPasteBehavior.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace NETworkManager.Controls;
+
+///
+/// Attached property that converts multiline text (e.g. a column pasted from Excel) into a
+/// semicolon-separated single line when pasted into an editable .
+///
+/// Without this the WPF TextBox inside the ComboBox ( is
+/// false) silently drops everything after the first line when multiline text is pasted.
+/// The conversion happens in before the paste
+/// command actually runs, by rewriting the clipboard content to the semicolon-separated form.
+///
+public static class ComboBoxPasteBehavior
+{
+ public static readonly DependencyProperty ConvertMultilineToSemicolonProperty =
+ DependencyProperty.RegisterAttached(
+ "ConvertMultilineToSemicolon",
+ typeof(bool),
+ typeof(ComboBoxPasteBehavior),
+ new PropertyMetadata(false, OnConvertMultilineToSemicolonChanged));
+
+ public static void SetConvertMultilineToSemicolon(UIElement element, bool value) =>
+ element.SetValue(ConvertMultilineToSemicolonProperty, value);
+
+ public static bool GetConvertMultilineToSemicolon(UIElement element) =>
+ (bool)element.GetValue(ConvertMultilineToSemicolonProperty);
+
+ private static void OnConvertMultilineToSemicolonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is not ComboBox comboBox)
+ return;
+
+ if ((bool)e.NewValue)
+ CommandManager.AddPreviewExecutedHandler(comboBox, OnPreviewExecuted);
+ else
+ CommandManager.RemovePreviewExecutedHandler(comboBox, OnPreviewExecuted);
+ }
+
+ private static void OnPreviewExecuted(object sender, ExecutedRoutedEventArgs e)
+ {
+ // Keyboard shortcut (Ctrl+V) and the paste context menu item both route through
+ // ApplicationCommands.Paste. Editing commands don't need to be handled here.
+ if (e.Command != ApplicationCommands.Paste)
+ return;
+
+ if (!Clipboard.ContainsText())
+ return;
+
+ var text = Clipboard.GetText();
+
+ // Only rewrite when there is actual multiline content (e.g. a column pasted from Excel).
+ if (!text.Contains('\n') && !text.Contains('\r'))
+ return;
+
+ var converted = string.Join(";", text
+ .Replace("\r\n", "\n")
+ .Replace('\r', '\n')
+ .Split('\n', StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => x.Trim()));
+
+ Clipboard.SetText(converted);
+ }
+}
diff --git a/Source/NETworkManager.Models/Network/HostRangeHelper.cs b/Source/NETworkManager.Models/Network/HostRangeHelper.cs
index f7b21cadef..e2ddeffde1 100644
--- a/Source/NETworkManager.Models/Network/HostRangeHelper.cs
+++ b/Source/NETworkManager.Models/Network/HostRangeHelper.cs
@@ -1,4 +1,5 @@
using NETworkManager.Utilities;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -17,14 +18,15 @@ namespace NETworkManager.Models.Network;
public static class HostRangeHelper
{
///
- /// Create a list of hosts from a string input like "10.0.0.1; example.com; 10.0.0.0/24"
+ /// Create a list of hosts from a string input like "10.0.0.1; example.com; 10.0.0.0/24".
+ /// Inputs can also be separated by newlines (e.g. pasted from Excel, one host/range per line).
///
- /// Hosts like "10.0.0.1; example.com; 10.0.0.0/24"
+ /// Hosts like "10.0.0.1; example.com; 10.0.0.0/24" or newline-separated lines
/// List of hosts.
public static IEnumerable CreateListFromInput(string hosts)
{
- return hosts.Replace(" ", "").Split(';')
- .Where(x => !string.IsNullOrEmpty(x))
+ return hosts.Replace(" ", "")
+ .Split([';', '\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToArray();
}
@@ -65,6 +67,22 @@ public static IEnumerable CreateListFromInput(string hosts)
break;
+ // 192.168.0.1-100
+ case var _ when RegexHelper.IPv4AddressShortRangeRegex().IsMatch(host):
+ var shortRange = host.Split('-');
+ var shortBase = shortRange[0][..shortRange[0].LastIndexOf('.')];
+
+ Parallel.For(IPv4Address.ToInt32(IPAddress.Parse(shortRange[0])),
+ IPv4Address.ToInt32(IPAddress.Parse($"{shortBase}.{shortRange[1]}")) + 1, (i, state) =>
+ {
+ if (ct.IsCancellationRequested)
+ state.Break();
+
+ hostsBag.Add((IPv4Address.FromInt32(i), string.Empty));
+ });
+
+ break;
+
// 192.168.0.0 - 192.168.0.100
case var _ when RegexHelper.IPv4AddressRangeRegex().IsMatch(host):
var range = host.Split('-');
diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs
index 0785fc2fa6..8462c2eedf 100644
--- a/Source/NETworkManager.Utilities/RegexHelper.cs
+++ b/Source/NETworkManager.Utilities/RegexHelper.cs
@@ -43,14 +43,30 @@ public static partial class RegexHelper
public static partial Regex IPv4AddressExtractRegex();
///
- /// Provides a compiles regular expression that matches IPv4 address ranges in the format "start-end" like
+ /// Represents a regular expression pattern that matches valid shorthand IPv4 address ranges like
+ /// "192.168.178.1-100" (base IP + last octet range).
+ ///
+ private const string IPv4AddressShortRangeValues =
+ @"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\-(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
+
+ ///
+ /// Provides a compiled regular expression that matches IPv4 address ranges in the format "start-end" like
/// "192.168.178.0-192.168.178.255".
- ///
+ ///
/// A instance that matches strings representing IPv4 address ranges, such as
/// "192.168.1.1-192.168.1.100".
[GeneratedRegex($"^{IPv4AddressValues}-{IPv4AddressValues}$")]
public static partial Regex IPv4AddressRangeRegex();
+ ///
+ /// Provides a compiled regular expression that matches shorthand IPv4 address ranges like
+ /// "192.168.178.1-100" (base IP followed by a last-octet range).
+ ///
+ /// A instance that matches strings representing shorthand IPv4 address ranges,
+ /// such as "192.168.1.1-100" (192.168.1.1 to 192.168.1.100).
+ [GeneratedRegex($"^{IPv4AddressShortRangeValues}$")]
+ public static partial Regex IPv4AddressShortRangeRegex();
+
///
/// Provides a compiled regular expression that matches valid IPv4 subnet mask like "255.255.0.0".
///
diff --git a/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs b/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
index a2674ce8ae..7f79762569 100644
--- a/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
+++ b/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
@@ -1,6 +1,7 @@
using NETworkManager.Localization.Resources;
using NETworkManager.Models.Network;
using NETworkManager.Utilities;
+using System;
using System.DirectoryServices.ActiveDirectory;
using System.Globalization;
using System.Net;
@@ -18,7 +19,9 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
if (value == null)
return new ValidationResult(false, Strings.EnterValidIPScanRange);
- foreach (var ipHostOrRange in ((string)value).Replace(" ", "").Split(';'))
+ foreach (var ipHostOrRange in ((string)value)
+ .Replace(" ", "")
+ .Split([';', '\r', '\n'], StringSplitOptions.RemoveEmptyEntries))
{
// 192.168.0.1
if (RegexHelper.IPv4AddressRegex().IsMatch(ipHostOrRange))
@@ -32,6 +35,19 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
if (RegexHelper.IPv4AddressSubnetmaskRegex().IsMatch(ipHostOrRange))
continue;
+ // 192.168.0.1-100
+ if (RegexHelper.IPv4AddressShortRangeRegex().IsMatch(ipHostOrRange))
+ {
+ var shortRange = ipHostOrRange.Split('-');
+ var shortBase = shortRange[0][..shortRange[0].LastIndexOf('.')];
+
+ if (IPv4Address.ToInt32(IPAddress.Parse(shortRange[0])) >
+ IPv4Address.ToInt32(IPAddress.Parse($"{shortBase}.{shortRange[1]}")))
+ isValid = false;
+
+ continue;
+ }
+
// 192.168.0.0 - 192.168.0.100
if (RegexHelper.IPv4AddressRangeRegex().IsMatch(ipHostOrRange))
{
diff --git a/Source/NETworkManager/Views/IPScannerView.xaml b/Source/NETworkManager/Views/IPScannerView.xaml
index c60cf86ed4..0a85dce0ba 100644
--- a/Source/NETworkManager/Views/IPScannerView.xaml
+++ b/Source/NETworkManager/Views/IPScannerView.xaml
@@ -68,6 +68,7 @@
ItemsSource="{Binding Path=HostHistoryView}"
mah:TextBoxHelper.Watermark="{x:Static Member=localization:StaticStrings.ExampleHostRange}"
IsReadOnly="{Binding Path=IsRunning}"
+ controls:ComboBoxPasteBehavior.ConvertMultilineToSemicolon="True"
Style="{StaticResource ResourceKey=HistoryComboBox}">
diff --git a/Source/NETworkManager/Views/PingMonitorHostView.xaml b/Source/NETworkManager/Views/PingMonitorHostView.xaml
index 0d11f8f732..8971374365 100644
--- a/Source/NETworkManager/Views/PingMonitorHostView.xaml
+++ b/Source/NETworkManager/Views/PingMonitorHostView.xaml
@@ -97,6 +97,7 @@
ItemsSource="{Binding Path=HostHistoryView}"
mah:TextBoxHelper.Watermark="{x:Static Member=localization:StaticStrings.ExampleHostRange}"
IsReadOnly="{Binding Path=IsRunning}"
+ controls:ComboBoxPasteBehavior.ConvertMultilineToSemicolon="True"
Style="{StaticResource ResourceKey=HistoryComboBox}">
diff --git a/Source/NETworkManager/Views/PortScannerView.xaml b/Source/NETworkManager/Views/PortScannerView.xaml
index 54bb2151fa..bbf3f36012 100644
--- a/Source/NETworkManager/Views/PortScannerView.xaml
+++ b/Source/NETworkManager/Views/PortScannerView.xaml
@@ -60,6 +60,7 @@
ItemsSource="{Binding HostHistoryView}"
mah:TextBoxHelper.Watermark="{x:Static localization:StaticStrings.ExampleHostRange}"
IsReadOnly="{Binding Path=IsRunning}"
+ controls:ComboBoxPasteBehavior.ConvertMultilineToSemicolon="True"
Style="{StaticResource HistoryComboBox}">
@@ -79,6 +80,7 @@
ItemsSource="{Binding PortsHistoryView}"
mah:TextBoxHelper.Watermark="{x:Static localization:StaticStrings.ExamplePortScanRange}"
IsReadOnly="{Binding Path=IsRunning}"
+ controls:ComboBoxPasteBehavior.ConvertMultilineToSemicolon="True"
Style="{StaticResource HistoryComboBox}">
diff --git a/Website/docs/application/ip-scanner.md b/Website/docs/application/ip-scanner.md
index 4f41a2f5d7..930f248c29 100644
--- a/Website/docs/application/ip-scanner.md
+++ b/Website/docs/application/ip-scanner.md
@@ -34,10 +34,12 @@ With the **IP Scanner** you can scan for active devices based on the hostname or
:::note
-Multiple inputs can be combined with a semicolon (`;`).
+Multiple inputs can be combined with a semicolon (`;`). A column pasted from Excel (one entry per line) is converted to the semicolon-separated form automatically.
Example: `10.0.0.0/24; 10.0.[10-20]1`
+Shorthand ranges like `192.168.0.1-100` (192.168.0.1 to 192.168.0.100) are also supported.
+
:::
### Toolbar
diff --git a/Website/docs/application/ping-monitor.md b/Website/docs/application/ping-monitor.md
index a1ad0a605d..d6aa1a8bb5 100644
--- a/Website/docs/application/ping-monitor.md
+++ b/Website/docs/application/ping-monitor.md
@@ -31,10 +31,12 @@ ICMP (Internet Control Message Protocol) is a network-layer protocol used to sen
:::note
-Multiple inputs can be combined with a semicolon (`;`).
+Multiple inputs can be combined with a semicolon (`;`). A column pasted from Excel (one entry per line) is converted to the semicolon-separated form automatically.
Example: `10.0.0.0/24; 10.0.[10-20]1`
+Shorthand ranges like `192.168.0.1-100` (192.168.0.1 to 192.168.0.100) are also supported.
+
:::
### Chart
diff --git a/Website/docs/application/port-scanner.md b/Website/docs/application/port-scanner.md
index 96d7632aca..5613179653 100644
--- a/Website/docs/application/port-scanner.md
+++ b/Website/docs/application/port-scanner.md
@@ -45,10 +45,12 @@ TCP (Transmission Control Protocol) is a connection-oriented transport-layer pro
:::note
-Multiple inputs can be combined with a semicolon (`;`).
+Multiple inputs can be combined with a semicolon (`;`). A column pasted from Excel (one entry per line) is converted to the semicolon-separated form automatically.
Example: `10.0.0.0/24; 10.0.[10-20]1` or `1-1024; 8080; 8443`
+Shorthand ranges like `192.168.0.1-100` (192.168.0.1 to 192.168.0.100) are also supported.
+
:::
### Toolbar
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 81231a080f..11ddf144a0 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -33,6 +33,10 @@ Release date: **xx.xx.2026**
- The collapsed/expanded state of profile groups (e.g. **linux-server**) is now remembered per profile file and shared across all tools, instead of resetting every time you switch tools or restart the application. [#3539](https://github.com/BornToBeRoot/NETworkManager/pull/3539)
+**IP Scanner, Port Scanner & Ping Monitor**
+
+- Host input fields now accept newline-separated hosts (one per line, e.g. pasted from Excel) in addition to the existing semicolon (`;`) separator. Shorthand IPv4 ranges like `192.168.0.1-100` are supported as well. [#3568](https://github.com/BornToBeRoot/NETworkManager/pull/3568)
+
**IP Scanner**
- Added `135` (RPC) and `9100` (raw printing) to the default **Ports** list used to detect if a host is reachable. [#3564](https://github.com/BornToBeRoot/NETworkManager/pull/3564)