From ff7024e2ea226017e291be76c4801fadcf568c3a Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sat, 11 Apr 2026 03:50:43 +0200
Subject: [PATCH 01/23] Feature: Firewall app
---
.../Firewall/Firewall.cs | 220 ++++++++++
.../Firewall/FirewallInterfaceType.cs | 27 ++
.../Firewall/FirewallPortLocation.cs | 17 +
.../Firewall/FirewallPortSpecification.cs | 66 +++
.../Firewall/FirewallProtocol.cs | 122 ++++++
.../Firewall/FirewallRule.cs | 182 +++++++++
.../Firewall/FirewallRuleAction.cs | 20 +
.../Firewall/FirewallRuleDirection.cs | 18 +
.../Firewall/FirewallRuleProgram.cs | 100 +++++
.../NETworkManager.Models.csproj | 4 +
.../Network/NetworkProfiles.cs | 27 ++
.../ViewModels/FirewallViewModel.cs | 237 ++++++++++-
Source/NETworkManager/Views/FirewallView.xaml | 380 +++++++++++++++++-
.../NETworkManager/Views/FirewallView.xaml.cs | 18 +
14 files changed, 1410 insertions(+), 28 deletions(-)
create mode 100644 Source/NETworkManager.Models/Firewall/Firewall.cs
create mode 100644 Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
create mode 100644 Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs
create mode 100644 Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs
create mode 100644 Source/NETworkManager.Models/Firewall/FirewallProtocol.cs
create mode 100644 Source/NETworkManager.Models/Firewall/FirewallRule.cs
create mode 100644 Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs
create mode 100644 Source/NETworkManager.Models/Firewall/FirewallRuleDirection.cs
create mode 100644 Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
create mode 100644 Source/NETworkManager.Models/Network/NetworkProfiles.cs
diff --git a/Source/NETworkManager.Models/Firewall/Firewall.cs b/Source/NETworkManager.Models/Firewall/Firewall.cs
new file mode 100644
index 0000000000..4d2484e5e4
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/Firewall.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using SMA = System.Management.Automation;
+using System.Threading.Tasks;
+using log4net;
+
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a firewall configuration and management class that provides functionalities
+/// for applying and managing firewall rules based on a specified profile.
+///
+public class Firewall
+{
+ #region Variables
+
+ ///
+ /// The Logger.
+ ///
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Firewall));
+
+ private const string RuleIdentifier = "NETworkManager_";
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Reads all Windows Firewall rules whose display name starts with
+ /// and maps them to objects.
+ ///
+ /// A task that resolves to the list of matching firewall rules.
+ public static async Task> GetRulesAsync()
+ {
+ return await Task.Run(() =>
+ {
+ var rules = new List();
+
+ using var ps = SMA.PowerShell.Create();
+
+ ps.AddScript($@"
+Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
+Import-Module NetSecurity -ErrorAction Stop
+Get-NetFirewallRule -DisplayName '{RuleIdentifier}*' | ForEach-Object {{
+ $rule = $_
+ $portFilter = $rule | Get-NetFirewallPortFilter
+ $appFilter = $rule | Get-NetFirewallApplicationFilter
+ [PSCustomObject]@{{
+ Id = $rule.ID
+ DisplayName = $rule.DisplayName
+ Enabled = ($rule.Enabled -eq 'True')
+ Description = $rule.Description
+ Direction = [string]$rule.Direction
+ Action = [string]$rule.Action
+ Protocol = $portFilter.Protocol
+ LocalPort = ($portFilter.LocalPort -join ',')
+ RemotePort = ($portFilter.RemotePort -join ',')
+ Profile = [string]$rule.Profile
+ InterfaceType = [string]$rule.InterfaceType
+ Program = $appFilter.Program
+ }}
+}}");
+
+ var results = ps.Invoke();
+
+ if (ps.Streams.Error.Count > 0)
+ {
+ foreach (var error in ps.Streams.Error)
+ Log.Warn($"PowerShell error: {error}");
+ }
+
+ foreach (var result in results)
+ {
+ try
+ {
+ var rule = new FirewallRule
+ {
+ Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
+ IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
+ Name = result.Properties["DisplayName"]?.Value?.ToString() ?? string.Empty,
+ Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
+ Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
+ Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
+ Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
+ LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
+ RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
+ NetworkProfiles = ParseProfile(result.Properties["Profile"]?.Value?.ToString()),
+ InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
+ };
+
+ var program = result.Properties["Program"]?.Value as string;
+
+ if (!string.IsNullOrWhiteSpace(program) && !program.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ rule.Program = new FirewallRuleProgram(program);
+
+ rules.Add(rule);
+ }
+ catch (Exception ex)
+ {
+ Log.Warn($"Failed to parse firewall rule: {ex.Message}");
+ }
+ }
+
+ return rules;
+ });
+ }
+
+ /// Parses a PowerShell direction string to .
+ private static FirewallRuleDirection ParseDirection(string value)
+ {
+ return value switch
+ {
+ "Outbound" => FirewallRuleDirection.Outbound,
+ _ => FirewallRuleDirection.Inbound,
+ };
+ }
+
+ /// Parses a PowerShell action string to .
+ private static FirewallRuleAction ParseAction(string value)
+ {
+ return value switch
+ {
+ "Allow" => FirewallRuleAction.Allow,
+ _ => FirewallRuleAction.Block,
+ };
+ }
+
+ /// Parses a PowerShell protocol string to .
+ private static FirewallProtocol ParseProtocol(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ return FirewallProtocol.Any;
+
+ return value.ToUpperInvariant() switch
+ {
+ "TCP" => FirewallProtocol.TCP,
+ "UDP" => FirewallProtocol.UDP,
+ "ICMPV4" => FirewallProtocol.ICMPv4,
+ "ICMPV6" => FirewallProtocol.ICMPv6,
+ "GRE" => FirewallProtocol.GRE,
+ "L2TP" => FirewallProtocol.L2TP,
+ _ => int.TryParse(value, out var proto) ? (FirewallProtocol)proto : FirewallProtocol.Any,
+ };
+ }
+
+ ///
+ /// Parses a comma-separated port string (e.g. "80,443,8080-8090") to a list of
+ /// objects. Returns an empty list for Any or blank input.
+ ///
+ private static List ParsePorts(string value)
+ {
+ var list = new List();
+
+ if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ return list;
+
+ foreach (var token in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
+ {
+ var dashIndex = token.IndexOf('-');
+
+ if (dashIndex > 0 &&
+ int.TryParse(token[..dashIndex], out var start) &&
+ int.TryParse(token[(dashIndex + 1)..], out var end))
+ {
+ list.Add(new FirewallPortSpecification(start, end));
+ }
+ else if (int.TryParse(token, out var port))
+ {
+ list.Add(new FirewallPortSpecification(port));
+ }
+ }
+
+ return list;
+ }
+
+ ///
+ /// Parses a PowerShell profile string (e.g. "Domain, Private") to a three-element boolean array
+ /// in the order Domain, Private, Public.
+ ///
+ private static bool[] ParseProfile(string value)
+ {
+ var profiles = new bool[3];
+
+ if (string.IsNullOrWhiteSpace(value))
+ return profiles;
+
+ if (value.Equals("Any", StringComparison.OrdinalIgnoreCase) ||
+ value.Equals("All", StringComparison.OrdinalIgnoreCase))
+ {
+ profiles[0] = profiles[1] = profiles[2] = true;
+ return profiles;
+ }
+
+ foreach (var token in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
+ {
+ switch (token)
+ {
+ case "Domain": profiles[0] = true; break;
+ case "Private": profiles[1] = true; break;
+ case "Public": profiles[2] = true; break;
+ }
+ }
+
+ return profiles;
+ }
+
+ /// Parses a PowerShell interface-type string to .
+ private static FirewallInterfaceType ParseInterfaceType(string value)
+ {
+ return value switch
+ {
+ "Wired" => FirewallInterfaceType.Wired,
+ "Wireless" => FirewallInterfaceType.Wireless,
+ "RemoteAccess" => FirewallInterfaceType.RemoteAccess,
+ _ => FirewallInterfaceType.Any,
+ };
+ }
+ #endregion
+}
diff --git a/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs b/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
new file mode 100644
index 0000000000..438291b6d9
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
@@ -0,0 +1,27 @@
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Defines the types of network interfaces that can be used in firewall rules.
+///
+public enum FirewallInterfaceType
+{
+ ///
+ /// Any interface type.
+ ///
+ Any = -1,
+
+ ///
+ /// Wired interface types, e.g. Ethernet.
+ ///
+ Wired,
+
+ ///
+ /// Wireless interface types, e.g. Wi-Fi.
+ ///
+ Wireless,
+
+ ///
+ /// Remote interface types, e.g. VPN, L2TP, OpenVPN, etc.
+ ///
+ RemoteAccess
+}
diff --git a/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs b/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs
new file mode 100644
index 0000000000..549cb35744
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs
@@ -0,0 +1,17 @@
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Ports of local host or remote host.
+///
+public enum FirewallPortLocation
+{
+ ///
+ /// Ports of local host.
+ ///
+ LocalPorts,
+
+ ///
+ /// Ports of remote host.
+ ///
+ RemotePorts
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs b/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs
new file mode 100644
index 0000000000..ae26b65d61
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs
@@ -0,0 +1,66 @@
+// ReSharper disable MemberCanBePrivate.Global
+// Needed for serialization.
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a specification for defining and validating firewall ports.
+///
+///
+/// This class is used to encapsulate rules and configurations for
+/// managing firewall port restrictions or allowances. It provides
+/// properties and methods to define a range of acceptable ports or
+/// individual port specifications.
+///
+public class FirewallPortSpecification
+{
+ ///
+ /// Gets or sets the start point or initial value of a process, range, or operation.
+ ///
+ ///
+ /// The Start property typically represents the beginning state or position for sequential
+ /// processing or iteration. The exact usage of this property may vary depending on the context of
+ /// the class or object it belongs to.
+ ///
+ public int Start { get; set; }
+
+ ///
+ /// Gets or sets the endpoint or final state of a process, range, or operation.
+ ///
+ ///
+ /// This property typically represents the termination position, time, or value
+ /// in a sequence, operation, or any bounded context. Its specific meaning may vary
+ /// depending on the context in which it is used.
+ ///
+ public int End { get; set; }
+
+ ///
+ /// For serializing.
+ ///
+ public FirewallPortSpecification()
+ {
+ Start = -1;
+ End = -1;
+ }
+
+ ///
+ /// Represents the specification for a firewall port, detailing its configuration
+ /// and rules for inbound or outbound network traffic.
+ ///
+ public FirewallPortSpecification(int start, int end = -1)
+ {
+ Start = start;
+ End = end;
+ }
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current instance of the object.
+ public override string ToString()
+ {
+ if (Start is 0)
+ return string.Empty;
+
+ return End is -1 or 0 ? $"{Start}" : $"{Start}-{End}";
+ }
+}
diff --git a/Source/NETworkManager.Models/Firewall/FirewallProtocol.cs b/Source/NETworkManager.Models/Firewall/FirewallProtocol.cs
new file mode 100644
index 0000000000..da54f95e6e
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallProtocol.cs
@@ -0,0 +1,122 @@
+// ReSharper disable InconsistentNaming
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Specifies the network protocols supported by the firewall configuration.
+/// Each protocol is represented by its respective protocol number as defined in
+/// the Internet Assigned Numbers Authority (IANA) protocol registry.
+/// This enumeration is used to identify traffic based on its protocol type
+/// for filtering or access control purposes in the firewall.
+///
+public enum FirewallProtocol
+{
+ ///
+ /// Denotes the Transmission Control Protocol (TCP) used in firewall configurations.
+ /// TCP is a fundamental protocol within the Internet Protocol Suite, ensuring reliable
+ /// communication by delivering a stream of data packets in sequence with error checking
+ /// between networked devices.
+ ///
+ TCP = 6,
+
+ ///
+ /// Represents the User Datagram Protocol (UDP) in the context of firewall rules.
+ /// UDP is a connectionless protocol within the Internet Protocol (IP) suite that
+ /// allows for minimal latency by transmitting datagrams without guaranteeing delivery,
+ /// order, or error recovery.
+ ///
+ UDP = 17,
+
+ ///
+ /// Represents the Internet Control Message Protocol (ICMPv4) in the context of firewall rules.
+ /// ICMP is used by network devices, such as routers, to send error messages and operational
+ /// information, indicating issues like unreachable network destinations.
+ ///
+ ICMPv4 = 1,
+
+ ///
+ /// Represents the Internet Control Message Protocol for IPv6 (ICMPv6) in the context of firewall rules.
+ /// ICMPv6 is a supporting protocol in the Internet Protocol version 6 (IPv6) suite and is used for
+ /// diagnostic and error-reporting purposes, as well as for functions such as Neighbor Discovery Protocol (NDP).
+ ///
+ ICMPv6 = 58,
+
+ ///
+ /// Represents the IPv6 Hop-by-Hop Option (HOPOPT) protocol in the context of firewall rules.
+ /// HOPOPT is a special protocol used in IPv6 for carrying optional information that must be examined
+ /// by each node along the packet's delivery path.
+ ///
+ HOPOPT = 0,
+
+ ///
+ /// Represents the Generic Routing Encapsulation (GRE) protocol in the context of firewall rules.
+ /// GRE is a tunneling protocol developed to encapsulate a wide variety of network layer protocols
+ /// inside virtual point-to-point links. It is commonly used in creating VPNs and enabling the
+ /// transport of multicast traffic and non-IP protocols across IP networks.
+ ///
+ GRE = 47,
+
+ ///
+ /// Represents the Internet Protocol Version 6 (IPv6) in the context of firewall rules.
+ /// IPv6 is the most recent version of the Internet Protocol (IP) and provides identification
+ /// and location addressing for devices across networks, enabling communication over the internet.
+ ///
+ IPv6 = 41,
+
+ ///
+ /// Represents the IPv6-Route protocol in the context of firewall rules.
+ /// IPv6-Route is used for routing header information in IPv6 packets, which
+ /// specifies the list of one or more intermediate nodes a packet should pass
+ /// through before reaching its destination.
+ ///
+ IPv6_Route = 43,
+
+ ///
+ /// Represents the IPv6 Fragmentation Header (IPv6_Frag) in the context of firewall rules.
+ /// The IPv6 Fragmentation Header is used to support fragmentation and reassembly of
+ /// packets in IPv6 networks. It facilitates handling packets that are too large to
+ /// fit in the path MTU (Maximum Transmission Unit) of the network segment.
+ ///
+ IPv6_Frag = 44,
+
+ ///
+ /// Represents the IPv6 No Next Header protocol in the context of firewall rules.
+ /// This protocol indicates that there is no next header following the current header in the IPv6 packet.
+ /// It is primarily used in cases where the payload does not require a specific transport protocol header.
+ ///
+ IPv6_NoNxt = 59,
+
+ ///
+ /// Represents the IPv6 Options (IPv6_Opts) protocol in the context of firewall rules.
+ /// IPv6 Options is a part of the IPv6 suite used for carrying optional internet-layer information
+ /// and additional headers for specific purposes, providing extensibility in IPv6 communication.
+ ///
+ IPv6_Opts = 60,
+
+ ///
+ /// Represents the Virtual Router Redundancy Protocol (VRRP) in the context of firewall rules.
+ /// VRRP is a network protocol that provides automatic assignment of available routers to
+ /// participating hosts, ensuring redundancy and high availability of router services.
+ ///
+ VRRP = 112,
+
+ ///
+ /// Represents the Pragmatic General Multicast (PGM) protocol in the context of firewall rules.
+ /// PGM is a reliable multicast transport protocol that ensures ordered, duplicate-free,
+ /// and scalable delivery of data in multicast-enabled networks.
+ ///
+ PGM = 113,
+
+ ///
+ /// Represents the Layer 2 Tunneling Protocol (L2TP) in the context of firewall rules.
+ /// L2TP is a tunneling protocol used to support virtual private networks (VPNs) or
+ /// as part of the delivery of services by Internet Service Providers (ISPs).
+ ///
+ L2TP = 115,
+
+ ///
+ /// Represents a wildcard protocol option to match any protocol in the context of firewall rules.
+ /// The "Any" value can be used to specify that the rule applies to all network protocols
+ /// without restriction or specificity.
+ ///
+ Any = 255
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRule.cs b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
new file mode 100644
index 0000000000..ff579ae05a
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
@@ -0,0 +1,182 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a security rule used within a firewall to control network traffic based on
+/// specific conditions such as IP addresses, ports, and protocols.
+///
+public class FirewallRule
+{
+ #region Variables
+
+ ///
+ /// Represents the name associated with the object.
+ ///
+ ///
+ /// This property is used to identify the object with a descriptive, human-readable name.
+ /// The value of this property is typically a string and can be used for display purposes
+ /// or as an identifier in various contexts.
+ ///
+ ///
+ /// Internal unique identifier of the rule (i.e. the value of $rule.Id in PowerShell).
+ /// Used to target the rule in Set-NetFirewallRule / Remove-NetFirewallRule calls.
+ ///
+ public string Id { get; set; }
+
+ public string Name { get; set; }
+
+ ///
+ /// Indicates whether the firewall rule is enabled.
+ ///
+ public bool IsEnabled { get; set; } = true;
+
+ ///
+ /// Represents a text-based explanation or information associated with an object.
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Represents the communication protocol to be used in the network configuration.
+ ///
+ public FirewallProtocol Protocol { get; set; } = FirewallProtocol.TCP;
+
+ ///
+ /// Defines the direction of traffic impacted by the rule or configuration.
+ ///
+ public FirewallRuleDirection Direction { get; set; } = FirewallRuleDirection.Inbound;
+
+ ///
+ /// Represents the entry point and core execution logic for an application.
+ ///
+ public FirewallRuleProgram Program { get; set; }
+
+ ///
+ /// Defines the local ports associated with the firewall rule.
+ ///
+ public List LocalPorts
+ {
+ get;
+ set
+ {
+ if (value is null)
+ {
+ field = [];
+ return;
+ }
+ field = value;
+ }
+ } = [];
+
+ ///
+ /// Defines the range of remote ports associated with the firewall rule.
+ ///
+ public List RemotePorts
+ {
+ get;
+ set
+ {
+ if (value is null)
+ {
+ field = [];
+ return;
+ }
+ field = value;
+ }
+ } = [];
+
+ ///
+ /// Network profiles in order Domain, Private, Public.
+ ///
+ public bool[] NetworkProfiles
+ {
+ get;
+ set
+ {
+ if (value?.Length is not 3)
+ return;
+ field = value;
+ }
+ } = new bool[3];
+
+ public FirewallInterfaceType InterfaceType { get; set; } = FirewallInterfaceType.Any;
+
+ ///
+ /// Represents the operation to be performed or executed.
+ ///
+ public FirewallRuleAction Action { get; set; } = FirewallRuleAction.Block;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Represents a rule within the firewall configuration.
+ /// Used to control network traffic based on specified criteria, such as
+ /// ports, protocols, the interface type, network profiles, and the used programs.
+ ///
+ public FirewallRule()
+ {
+
+ }
+ #endregion
+
+ #region Display properties
+
+ /// Local ports as a human-readable string (e.g. "80; 443; 8080-8090").
+ public string LocalPortsDisplay => PortsToString(LocalPorts);
+
+ /// Remote ports as a human-readable string (e.g. "80; 443").
+ public string RemotePortsDisplay => PortsToString(RemotePorts);
+
+ ///
+ /// Network profiles (Domain / Private / Public) as a comma-separated string.
+ /// Returns "Any" when all three are set.
+ ///
+ public string NetworkProfilesDisplay
+ {
+ get
+ {
+ if (NetworkProfiles.Length == 3 && NetworkProfiles.All(x => x))
+ return "Any";
+
+ var names = new List(3);
+ if (NetworkProfiles.Length > 0 && NetworkProfiles[0]) names.Add("Domain");
+ if (NetworkProfiles.Length > 1 && NetworkProfiles[1]) names.Add("Private");
+ if (NetworkProfiles.Length > 2 && NetworkProfiles[2]) names.Add("Public");
+
+ return names.Count == 0 ? "-" : string.Join(", ", names);
+ }
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Converts a collection of port numbers to a single, comma-separated string representation.
+ ///
+ /// A collection of integers representing port numbers.
+ /// Separator character to use
+ /// Separate entries with a space.
+ /// A separated string containing all the port numbers from the input collection.
+ public static string PortsToString(List ports, char separator = ';', bool spacing = true)
+ {
+ if (ports.Count is 0)
+ return string.Empty;
+
+ var whitespace = spacing ? " " : string.Empty;
+
+ var builder = new StringBuilder();
+
+ foreach (var port in ports)
+ builder.Append($"{port}{separator}{whitespace}");
+
+ var offset = spacing ? 2 : 1;
+
+ return builder.ToString()[..^offset];
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs b/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs
new file mode 100644
index 0000000000..eaaa63cfc4
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs
@@ -0,0 +1,20 @@
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents the action, if the rule filter applies.
+///
+public enum FirewallRuleAction
+{
+ ///
+ /// Represents the action to block network traffic in a firewall rule.
+ ///
+ Block,
+
+ ///
+ /// Represents the action to allow network traffic.
+ ///
+ Allow,
+
+ // Unsupported for now
+ //AllowIPsec
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRuleDirection.cs b/Source/NETworkManager.Models/Firewall/FirewallRuleDirection.cs
new file mode 100644
index 0000000000..ddd72c116f
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleDirection.cs
@@ -0,0 +1,18 @@
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a firewall rule direction that allows or processes network traffic
+/// incoming to the system or network from external sources.
+///
+public enum FirewallRuleDirection
+{
+ ///
+ /// Inbound packets.
+ ///
+ Inbound,
+
+ ///
+ /// Outbound packets.
+ ///
+ Outbound
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs b/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
new file mode 100644
index 0000000000..12133b1344
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
@@ -0,0 +1,100 @@
+using System;
+using System.IO;
+using System.Text.Json.Serialization;
+using System.Xml.Serialization;
+
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a program associated with a firewall rule.
+///
+public class FirewallRuleProgram : ICloneable
+{
+ #region Variables
+ ///
+ /// Program to apply rule to.
+ ///
+ [JsonIgnore]
+ [XmlIgnore]
+ public FileInfo Executable {
+ private set;
+ get
+ {
+ if (field is null && Name is not null)
+ field = new FileInfo(Name);
+
+ return field;
+ }
+ }
+
+ ///
+ /// Represents the name associated with the object.
+ ///
+ public string Name
+ {
+ get;
+ // Public modifier required for deserialization
+ // ReSharper disable once MemberCanBePrivate.Global
+ // ReSharper disable once PropertyCanBeMadeInitOnly.Global
+ set
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return;
+
+ Executable = new FileInfo(value);
+ field = value;
+ }
+ }
+ #endregion
+
+ #region Constructor
+ ///
+ /// Public empty constructor is required for de-/serialization.
+ ///
+ // ReSharper disable once MemberCanBePrivate.Global
+ public FirewallRuleProgram()
+ {
+ }
+
+ ///
+ /// Construct program reference for firewall rule.
+ ///
+ ///
+ public FirewallRuleProgram(string pathToExe)
+ {
+ ArgumentNullException.ThrowIfNull(pathToExe);
+ var exe = new FileInfo(pathToExe);
+ Executable = exe;
+ Name = exe.FullName;
+ }
+ #endregion
+
+ #region Methods
+ ///
+ /// Convert the full file path to string.
+ ///
+ ///
+ public override string ToString()
+ {
+ return Executable?.FullName;
+ }
+
+ ///
+ /// Clone instance.
+ ///
+ /// An instance clone.
+ public object Clone()
+ {
+ try
+ {
+ return new FirewallRuleProgram(Executable?.FullName);
+ }
+ catch (ArgumentNullException)
+ {
+ return new FirewallRuleProgram();
+ }
+
+ }
+
+ #endregion
+}
diff --git a/Source/NETworkManager.Models/NETworkManager.Models.csproj b/Source/NETworkManager.Models/NETworkManager.Models.csproj
index c913dd5488..d5123a011d 100644
--- a/Source/NETworkManager.Models/NETworkManager.Models.csproj
+++ b/Source/NETworkManager.Models/NETworkManager.Models.csproj
@@ -61,4 +61,8 @@
PreserveNewest
+
+
+
+
diff --git a/Source/NETworkManager.Models/Network/NetworkProfiles.cs b/Source/NETworkManager.Models/Network/NetworkProfiles.cs
new file mode 100644
index 0000000000..4d7d52d203
--- /dev/null
+++ b/Source/NETworkManager.Models/Network/NetworkProfiles.cs
@@ -0,0 +1,27 @@
+namespace NETworkManager.Models.Network;
+
+///
+/// Defines the network profile detected by Windows.
+///
+public enum NetworkProfiles
+{
+ ///
+ /// Network profile is not configured.
+ ///
+ NotConfigured = -1,
+
+ ///
+ /// Network has an Active Directory (AD) controller and you are authenticated.
+ ///
+ Domain,
+
+ ///
+ /// Network is private. Firewall will allow most connections.
+ ///
+ Private,
+
+ ///
+ /// Network is public. Firewall will block most connections.
+ ///
+ Public
+}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index a11d89993d..29fec76905 100644
--- a/Source/NETworkManager/ViewModels/FirewallViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
@@ -1,5 +1,8 @@
-using System.Threading.Tasks;
+using System.Collections;
+using System.Threading.Tasks;
+using log4net;
using NETworkManager.Localization.Resources;
+using NETworkManager.Models.Firewall;
namespace NETworkManager.ViewModels;
@@ -24,12 +27,125 @@ namespace NETworkManager.ViewModels;
public class FirewallViewModel : ViewModelBase, IProfileManager
{
#region Variables
-
+
+ private static readonly ILog Log = LogManager.GetLogger(typeof(FirewallViewModel));
+
private readonly DispatcherTimer _searchDispatcherTimer = new();
private bool _searchDisabled;
private readonly bool _isLoading;
private bool _isViewActive = true;
+ #region Rules
+
+ ///
+ /// Gets the loaded firewall rules.
+ ///
+ public ObservableCollection Results { get; } = [];
+
+ ///
+ /// Gets the filtered/sorted view over .
+ ///
+ public ICollectionView ResultsView { get; }
+
+ ///
+ /// Gets or sets the currently selected firewall rule.
+ ///
+ public FirewallRule SelectedResult
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets the list of selected firewall rules (multi-select).
+ ///
+ public IList SelectedResults
+ {
+ get;
+ set
+ {
+ if (Equals(value, field))
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = new ArrayList();
+
+ ///
+ /// Gets or sets the search text for filtering rules.
+ ///
+ public string RulesSearch
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ ResultsView.Refresh();
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets whether a refresh is currently running.
+ ///
+ public bool IsRefreshing
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets whether the status message bar is shown.
+ ///
+ public bool IsStatusMessageDisplayed
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets the status message text.
+ ///
+ public string StatusMessage
+ {
+ get;
+ private set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ #endregion
+
#region Profiles
///
@@ -243,6 +359,25 @@ public FirewallViewModel()
{
_isLoading = true;
+ // Rules
+ ResultsView = CollectionViewSource.GetDefaultView(Results);
+ ResultsView.Filter = o =>
+ {
+ if (string.IsNullOrEmpty(RulesSearch))
+ return true;
+
+ if (o is not FirewallRule rule)
+ return false;
+
+ return rule.Name.IndexOf(RulesSearch, StringComparison.OrdinalIgnoreCase) > -1 ||
+ rule.Protocol.ToString().IndexOf(RulesSearch, StringComparison.OrdinalIgnoreCase) > -1 ||
+ rule.Action.ToString().IndexOf(RulesSearch, StringComparison.OrdinalIgnoreCase) > -1 ||
+ rule.Direction.ToString().IndexOf(RulesSearch, StringComparison.OrdinalIgnoreCase) > -1;
+ };
+
+ // Load firewall rules
+ Refresh(true).ConfigureAwait(false);
+
// Profiles
CreateTags();
@@ -280,17 +415,59 @@ private void LoadSettings()
#region ICommand & Actions
- ///
- /// Gets the command to add a new firewall entry.
- ///
+ /// Gets the command to refresh the firewall rules.
+ public ICommand RefreshCommand => new RelayCommand(_ => RefreshAction().ConfigureAwait(false), Refresh_CanExecute);
+
+ private bool Refresh_CanExecute(object _) => !IsRefreshing;
+
+ private async Task RefreshAction() => await Refresh();
+
+ /// Gets the command to add a new firewall entry.
public ICommand AddEntryCommand => new RelayCommand(_ => AddEntryAction());
- ///
- /// Action to add a new firewall entry.
- ///
private void AddEntryAction()
{
- MessageBox.Show("Not implemented");
+ // TODO: open AddFirewallRuleDialog
+ }
+
+ /// Gets the command to enable the selected firewall entry.
+ public ICommand EnableEntryCommand => new RelayCommand(_ => EnableEntryAction(), _ => SelectedResult is { IsEnabled: false });
+
+ private void EnableEntryAction()
+ {
+ // TODO: call Firewall.SetRuleEnabledAsync(SelectedResult, true)
+ }
+
+ /// Gets the command to disable the selected firewall entry.
+ public ICommand DisableEntryCommand => new RelayCommand(_ => DisableEntryAction(), _ => SelectedResult is { IsEnabled: true });
+
+ private void DisableEntryAction()
+ {
+ // TODO: call Firewall.SetRuleEnabledAsync(SelectedResult, false)
+ }
+
+ /// Gets the command to edit the selected firewall entry.
+ public ICommand EditEntryCommand => new RelayCommand(_ => EditEntryAction(), _ => SelectedResult != null);
+
+ private void EditEntryAction()
+ {
+ // TODO: open EditFirewallRuleDialog
+ }
+
+ /// Gets the command to delete the selected firewall entry.
+ public ICommand DeleteEntryCommand => new RelayCommand(_ => DeleteEntryAction(), _ => SelectedResult != null);
+
+ private void DeleteEntryAction()
+ {
+ // TODO: confirm and call Firewall.DeleteRuleAsync
+ }
+
+ /// Gets the command to export the firewall rules.
+ public ICommand ExportCommand => new RelayCommand(_ => ExportAction());
+
+ private void ExportAction()
+ {
+ // TODO: implement export
}
///
@@ -493,6 +670,48 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
#endregion
#region Methods
+
+ ///
+ /// Loads firewall rules from Windows via PowerShell and populates .
+ ///
+ private async Task Refresh(bool init = false)
+ {
+ if (IsRefreshing)
+ return;
+
+ IsRefreshing = true;
+ StatusMessage = Strings.RefreshingDots;
+ IsStatusMessageDisplayed = true;
+
+ if (!init)
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
+
+ try
+ {
+ var rules = await Firewall.GetRulesAsync();
+
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ Results.Clear();
+
+ foreach (var rule in rules)
+ Results.Add(rule);
+ });
+
+ StatusMessage = string.Format(Strings.ReloadedAtX, DateTime.Now.ToShortTimeString());
+ IsStatusMessageDisplayed = true;
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while loading firewall rules", ex);
+
+ StatusMessage = string.Format(Strings.FailedToLoadFirewallRulesMessage, ex.Message);
+ IsStatusMessageDisplayed = true;
+ }
+
+ IsRefreshing = false;
+ }
+
///
/// Sets the IsExpanded property for all profile groups.
///
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index 656a84922b..435e1f145c 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -37,32 +37,371 @@
+
+
+
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/Source/NETworkManager/Views/FirewallView.xaml.cs b/Source/NETworkManager/Views/FirewallView.xaml.cs
index bc108f1bf9..2793e4ad87 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml.cs
+++ b/Source/NETworkManager/Views/FirewallView.xaml.cs
@@ -1,6 +1,7 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
+using System.Windows.Media;
using NETworkManager.ViewModels;
namespace NETworkManager.Views;
@@ -35,6 +36,23 @@ private void ContextMenu_Opened(object sender, RoutedEventArgs e)
if (sender is ContextMenu menu)
menu.DataContext = _viewModel;
}
+
+ ///
+ /// Toggles the row details visibility when the expand/collapse chevron is clicked.
+ ///
+ private void ExpandRowDetails_OnClick(object sender, RoutedEventArgs e)
+ {
+ for (var visual = sender as Visual; visual != null; visual = VisualTreeHelper.GetParent(visual) as Visual)
+ {
+ if (visual is not DataGridRow row)
+ continue;
+
+ row.DetailsVisibility =
+ row.DetailsVisibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
+
+ break;
+ }
+ }
private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
From c3bccb0394a11f23e12f5f5345013e53b2d3aac7 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sat, 11 Apr 2026 04:00:57 +0200
Subject: [PATCH 02/23] Update
Source/NETworkManager.Models/Firewall/FirewallRule.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../Firewall/FirewallRule.cs | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRule.cs b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
index ff579ae05a..373cc4e06e 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallRule.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
@@ -166,17 +166,10 @@ public string NetworkProfilesDisplay
{
if (ports.Count is 0)
return string.Empty;
-
- var whitespace = spacing ? " " : string.Empty;
-
- var builder = new StringBuilder();
-
- foreach (var port in ports)
- builder.Append($"{port}{separator}{whitespace}");
-
- var offset = spacing ? 2 : 1;
-
- return builder.ToString()[..^offset];
+
+ var delimiter = spacing ? $"{separator} " : separator.ToString();
+
+ return string.Join(delimiter, ports.Select(port => port.ToString()));
}
#endregion
}
\ No newline at end of file
From 7cf2ffd394815bd4cd61540f5ecc77e52e956e12 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 11 Apr 2026 02:02:15 +0000
Subject: [PATCH 03/23] Fix XML doc comments for Id/Name properties in
FirewallRule.cs
Agent-Logs-Url: https://github.com/BornToBeRoot/NETworkManager/sessions/5215e397-497d-4847-a3f7-a7454cafebb3
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
---
.../NETworkManager.Models/Firewall/FirewallRule.cs | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRule.cs b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
index 373cc4e06e..53fa4e98c5 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallRule.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
@@ -12,20 +12,16 @@ public class FirewallRule
{
#region Variables
- ///
- /// Represents the name associated with the object.
- ///
- ///
- /// This property is used to identify the object with a descriptive, human-readable name.
- /// The value of this property is typically a string and can be used for display purposes
- /// or as an identifier in various contexts.
- ///
///
/// Internal unique identifier of the rule (i.e. the value of $rule.Id in PowerShell).
/// Used to target the rule in Set-NetFirewallRule / Remove-NetFirewallRule calls.
///
public string Id { get; set; }
+ ///
+ /// Human-readable display name of the firewall rule. Used for display purposes
+ /// or as an identifier in various contexts.
+ ///
public string Name { get; set; }
///
From 0ea864c13d3fc9265718923c89b0a22bb5425793 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sat, 11 Apr 2026 05:04:31 +0200
Subject: [PATCH 04/23] Feature: Add translation
---
.../FirewallInterfaceTypeToStringConverter.cs | 41 +++++
.../FirewallProtocolToStringConverter.cs | 41 +++++
.../ResourceIdentifier.cs | 4 +-
.../Resources/Strings.resx | 57 +++++++
.../Firewall/Firewall.cs | 54 +++++--
.../Firewall/FirewallRule.cs | 53 ++++++-
Source/NETworkManager/Views/FirewallView.xaml | 149 ++++++++++++++----
7 files changed, 350 insertions(+), 49 deletions(-)
create mode 100644 Source/NETworkManager.Converters/FirewallInterfaceTypeToStringConverter.cs
create mode 100644 Source/NETworkManager.Converters/FirewallProtocolToStringConverter.cs
diff --git a/Source/NETworkManager.Converters/FirewallInterfaceTypeToStringConverter.cs b/Source/NETworkManager.Converters/FirewallInterfaceTypeToStringConverter.cs
new file mode 100644
index 0000000000..f15e2ac52b
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallInterfaceTypeToStringConverter.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization;
+using NETworkManager.Models.Firewall;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert to translated or vice versa.
+///
+public sealed class FirewallInterfaceTypeToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to translated .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Translated .
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not FirewallInterfaceType interfaceType
+ ? "-/-"
+ : ResourceTranslator.Translate(ResourceIdentifier.FirewallInterfaceType, interfaceType);
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Converters/FirewallProtocolToStringConverter.cs b/Source/NETworkManager.Converters/FirewallProtocolToStringConverter.cs
new file mode 100644
index 0000000000..daced724af
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallProtocolToStringConverter.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization;
+using NETworkManager.Models.Firewall;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert to translated or vice versa.
+///
+public sealed class FirewallProtocolToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to translated .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Translated .
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not FirewallProtocol protocol
+ ? "-/-"
+ : ResourceTranslator.Translate(ResourceIdentifier.FirewallProtocol, protocol);
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Localization/ResourceIdentifier.cs b/Source/NETworkManager.Localization/ResourceIdentifier.cs
index 0cecf42520..803af9f1ec 100644
--- a/Source/NETworkManager.Localization/ResourceIdentifier.cs
+++ b/Source/NETworkManager.Localization/ResourceIdentifier.cs
@@ -22,5 +22,7 @@ public enum ResourceIdentifier
TcpState,
Theme,
TimeUnit,
- WiFiConnectionStatus
+ WiFiConnectionStatus,
+ FirewallProtocol,
+ FirewallInterfaceType
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 789507cf64..6d5f3e12d6 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -4046,4 +4046,61 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Failed to load firewall rules. {0}
+
+ Any
+
+
+ TCP
+
+
+ UDP
+
+
+ ICMPv4
+
+
+ ICMPv6
+
+
+ HOPOPT
+
+
+ GRE
+
+
+ IPv6
+
+
+ IPv6-Route
+
+
+ IPv6-Frag
+
+
+ IPv6-NoNxt
+
+
+ IPv6-Opts
+
+
+ VRRP
+
+
+ PGM
+
+
+ L2TP
+
+
+ Any
+
+
+ Wired
+
+
+ Wireless
+
+
+ Remote access
+
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/Firewall.cs b/Source/NETworkManager.Models/Firewall/Firewall.cs
index 4d2484e5e4..5222e34eda 100644
--- a/Source/NETworkManager.Models/Firewall/Firewall.cs
+++ b/Source/NETworkManager.Models/Firewall/Firewall.cs
@@ -43,9 +43,11 @@ public static async Task> GetRulesAsync()
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
Import-Module NetSecurity -ErrorAction Stop
Get-NetFirewallRule -DisplayName '{RuleIdentifier}*' | ForEach-Object {{
- $rule = $_
- $portFilter = $rule | Get-NetFirewallPortFilter
- $appFilter = $rule | Get-NetFirewallApplicationFilter
+ $rule = $_
+ $portFilter = $rule | Get-NetFirewallPortFilter
+ $addressFilter = $rule | Get-NetFirewallAddressFilter
+ $appFilter = $rule | Get-NetFirewallApplicationFilter
+ $ifTypeFilter = $rule | Get-NetFirewallInterfaceTypeFilter
[PSCustomObject]@{{
Id = $rule.ID
DisplayName = $rule.DisplayName
@@ -54,10 +56,12 @@ public static async Task> GetRulesAsync()
Direction = [string]$rule.Direction
Action = [string]$rule.Action
Protocol = $portFilter.Protocol
- LocalPort = ($portFilter.LocalPort -join ',')
- RemotePort = ($portFilter.RemotePort -join ',')
+ LocalPort = ($portFilter.LocalPort -join ',')
+ RemotePort = ($portFilter.RemotePort -join ',')
+ LocalAddress = ($addressFilter.LocalAddress -join ',')
+ RemoteAddress = ($addressFilter.RemoteAddress -join ',')
Profile = [string]$rule.Profile
- InterfaceType = [string]$rule.InterfaceType
+ InterfaceType = [string]$ifTypeFilter.InterfaceType
Program = $appFilter.Program
}}
}}");
@@ -74,19 +78,25 @@ public static async Task> GetRulesAsync()
{
try
{
+ var displayName = result.Properties["DisplayName"]?.Value?.ToString() ?? string.Empty;
+
var rule = new FirewallRule
{
- Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
- IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
- Name = result.Properties["DisplayName"]?.Value?.ToString() ?? string.Empty,
- Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
- Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
- Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
- Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
- LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
- RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
+ Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
+ IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
+ Name = displayName.StartsWith(RuleIdentifier, StringComparison.Ordinal)
+ ? displayName[RuleIdentifier.Length..]
+ : displayName,
+ Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
+ Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
+ Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
+ Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
+ LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
+ RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
+ LocalAddresses = ParseAddresses(result.Properties["LocalAddress"]?.Value?.ToString()),
+ RemoteAddresses = ParseAddresses(result.Properties["RemoteAddress"]?.Value?.ToString()),
NetworkProfiles = ParseProfile(result.Properties["Profile"]?.Value?.ToString()),
- InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
+ InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
};
var program = result.Properties["Program"]?.Value as string;
@@ -205,6 +215,18 @@ private static bool[] ParseProfile(string value)
return profiles;
}
+ ///
+ /// Parses a comma-separated address string (e.g. "192.168.1.0/24,LocalSubnet") to a list of
+ /// address strings. Returns an empty list for Any or blank input.
+ ///
+ private static List ParseAddresses(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ return [];
+
+ return [.. value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
+ }
+
/// Parses a PowerShell interface-type string to .
private static FirewallInterfaceType ParseInterfaceType(string value)
{
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRule.cs b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
index 53fa4e98c5..1af34b849e 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallRule.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
@@ -49,6 +49,42 @@ public class FirewallRule
///
public FirewallRuleProgram Program { get; set; }
+ ///
+ /// Local IP addresses or address specifiers (e.g. "192.168.1.0/24", "LocalSubnet").
+ /// An empty list means "Any".
+ ///
+ public List LocalAddresses
+ {
+ get;
+ set
+ {
+ if (value is null)
+ {
+ field = [];
+ return;
+ }
+ field = value;
+ }
+ } = [];
+
+ ///
+ /// Remote IP addresses or address specifiers (e.g. "10.0.0.0/8", "Internet").
+ /// An empty list means "Any".
+ ///
+ public List RemoteAddresses
+ {
+ get;
+ set
+ {
+ if (value is null)
+ {
+ field = [];
+ return;
+ }
+ field = value;
+ }
+ } = [];
+
///
/// Defines the local ports associated with the firewall rule.
///
@@ -121,11 +157,20 @@ public FirewallRule()
#region Display properties
- /// Local ports as a human-readable string (e.g. "80; 443; 8080-8090").
- public string LocalPortsDisplay => PortsToString(LocalPorts);
+ /// Program path, or null when no program restriction is set.
+ public string ProgramDisplay => Program?.ToString();
+
+ /// Local addresses as a human-readable string (e.g. "192.168.1.0/24; LocalSubnet"). Returns null when unrestricted.
+ public string LocalAddressesDisplay => LocalAddresses.Count == 0 ? null : string.Join("; ", LocalAddresses);
+
+ /// Remote addresses as a human-readable string (e.g. "10.0.0.0/8"). Returns null when unrestricted.
+ public string RemoteAddressesDisplay => RemoteAddresses.Count == 0 ? null : string.Join("; ", RemoteAddresses);
+
+ /// Local ports as a human-readable string (e.g. "80; 443; 8080-8090"). Returns null when unrestricted.
+ public string LocalPortsDisplay => LocalPorts.Count == 0 ? null : PortsToString(LocalPorts);
- /// Remote ports as a human-readable string (e.g. "80; 443").
- public string RemotePortsDisplay => PortsToString(RemotePorts);
+ /// Remote ports as a human-readable string (e.g. "80; 443"). Returns null when unrestricted.
+ public string RemotePortsDisplay => RemotePorts.Count == 0 ? null : PortsToString(RemotePorts);
///
/// Network profiles (Domain / Private / Public) as a comma-separated string.
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index 435e1f145c..f0bace57df 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -12,6 +12,7 @@
xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization"
xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings"
xmlns:controls="clr-namespace:NETworkManager.Controls;assembly=NETworkManager.Controls"
+ xmlns:firewall="clr-namespace:NETworkManager.Models.Firewall;assembly=NETworkManager.Models"
xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF"
xmlns:networkManager="clr-namespace:NETworkManager"
@@ -22,6 +23,8 @@
+
+
@@ -204,15 +207,21 @@
+
+
+
-
+
-
+
@@ -251,7 +260,7 @@
-
+
@@ -268,13 +277,13 @@
-
+
-
+
@@ -283,35 +292,43 @@
-
-
-
+ MinWidth="100">
+
+
+
+
+
+
+
+
+ MinWidth="100">
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -444,7 +537,7 @@
-
Date: Sat, 11 Apr 2026 05:47:01 +0200
Subject: [PATCH 05/23] Feature: Add translation and export
---
...irewallNetworkProfilesToStringConverter.cs | 45 ++++++
.../Resources/Strings.Designer.cs | 20 ++-
.../Resources/Strings.resx | 6 +
.../Export/ExportManager.FirewallRule.cs | 138 ++++++++++++++++++
.../GlobalStaticConfiguration.cs | 3 +
.../NETworkManager.Settings/SettingsInfo.cs | 26 ++++
.../ViewModels/FirewallViewModel.cs | 73 +++++++--
Source/NETworkManager/Views/FirewallView.xaml | 5 +-
8 files changed, 298 insertions(+), 18 deletions(-)
create mode 100644 Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
create mode 100644 Source/NETworkManager.Models/Export/ExportManager.FirewallRule.cs
diff --git a/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs b/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
new file mode 100644
index 0000000000..10ce69e7e3
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization.Resources;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert a array (Domain, Private, Public) representing firewall network
+/// profiles to a localized , or vice versa.
+///
+public sealed class FirewallNetworkProfilesToStringConverter : IValueConverter
+{
+ ///
+ /// Convert a array (Domain, Private, Public) to a localized .
+ /// Returns null when all three profiles are active so that a TargetNullValue binding
+ /// can supply the translated "Any" label.
+ ///
+ /// A array with exactly three elements.
+ ///
+ ///
+ ///
+ /// Localized, comma-separated profile list (e.g. "Domain, Private, Public").
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is not bool[] { Length: 3 } profiles)
+ return "-/-";
+
+ var names = new List(3);
+ if (profiles[0]) names.Add(Strings.Domain);
+ if (profiles[1]) names.Add(Strings.Private);
+ if (profiles[2]) names.Add(Strings.Public);
+
+ return names.Count == 0 ? "-" : string.Join(", ", names);
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 68086ee533..d263245e07 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -7343,7 +7343,25 @@ public static string Program {
return ResourceManager.GetString("Program", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to Private.
+ ///
+ public static string Private {
+ get {
+ return ResourceManager.GetString("Private", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Public.
+ ///
+ public static string Public {
+ get {
+ return ResourceManager.GetString("Public", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Protocol.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 6d5f3e12d6..1fea3de8ec 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -2151,6 +2151,12 @@ is disabled!
Program
+
+ Private
+
+
+ Public
+
The selected custom command will be deleted permanently.
diff --git a/Source/NETworkManager.Models/Export/ExportManager.FirewallRule.cs b/Source/NETworkManager.Models/Export/ExportManager.FirewallRule.cs
new file mode 100644
index 0000000000..ec8cb81f8f
--- /dev/null
+++ b/Source/NETworkManager.Models/Export/ExportManager.FirewallRule.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using NETworkManager.Models.Firewall;
+using NETworkManager.Utilities;
+using Newtonsoft.Json;
+
+namespace NETworkManager.Models.Export;
+
+public static partial class ExportManager
+{
+ ///
+ /// Method to export objects from type to a file.
+ ///
+ /// Path to the export file.
+ /// Allowed are CSV, XML or JSON.
+ /// Objects as to export.
+ public static void Export(string filePath, ExportFileType fileType, IReadOnlyList collection)
+ {
+ switch (fileType)
+ {
+ case ExportFileType.Csv:
+ CreateCsv(collection, filePath);
+ break;
+ case ExportFileType.Xml:
+ CreateXml(collection, filePath);
+ break;
+ case ExportFileType.Json:
+ CreateJson(collection, filePath);
+ break;
+ case ExportFileType.Txt:
+ default:
+ throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null);
+ }
+ }
+
+ ///
+ /// Creates a CSV file from the given collection.
+ ///
+ private static void CreateCsv(IEnumerable collection, string filePath)
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine(
+ $"{nameof(FirewallRule.Name)}," +
+ $"{nameof(FirewallRule.IsEnabled)}," +
+ $"{nameof(FirewallRule.Direction)}," +
+ $"{nameof(FirewallRule.Action)}," +
+ $"{nameof(FirewallRule.Protocol)}," +
+ $"{nameof(FirewallRule.LocalPorts)}," +
+ $"{nameof(FirewallRule.RemotePorts)}," +
+ $"{nameof(FirewallRule.LocalAddresses)}," +
+ $"{nameof(FirewallRule.RemoteAddresses)}," +
+ $"{nameof(FirewallRule.NetworkProfiles)}," +
+ $"{nameof(FirewallRule.InterfaceType)}," +
+ $"{nameof(FirewallRule.Program)}," +
+ $"{nameof(FirewallRule.Description)}");
+
+ foreach (var rule in collection)
+ sb.AppendLine(
+ $"{CsvHelper.QuoteString(rule.Name)}," +
+ $"{rule.IsEnabled}," +
+ $"{rule.Direction}," +
+ $"{rule.Action}," +
+ $"{rule.Protocol}," +
+ $"{CsvHelper.QuoteString(rule.LocalPortsDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.RemotePortsDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.LocalAddressesDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.RemoteAddressesDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.NetworkProfilesDisplay)}," +
+ $"{rule.InterfaceType}," +
+ $"{CsvHelper.QuoteString(rule.ProgramDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.Description)}");
+
+ File.WriteAllText(filePath, sb.ToString());
+ }
+
+ ///
+ /// Creates an XML file from the given collection.
+ ///
+ private static void CreateXml(IEnumerable collection, string filePath)
+ {
+ var document = new XDocument(DefaultXDeclaration,
+ new XElement(nameof(ApplicationName.Firewall),
+ new XElement(nameof(FirewallRule) + "s",
+ from rule in collection
+ select new XElement(nameof(FirewallRule),
+ new XElement(nameof(FirewallRule.Name), rule.Name),
+ new XElement(nameof(FirewallRule.IsEnabled), rule.IsEnabled),
+ new XElement(nameof(FirewallRule.Direction), rule.Direction),
+ new XElement(nameof(FirewallRule.Action), rule.Action),
+ new XElement(nameof(FirewallRule.Protocol), rule.Protocol),
+ new XElement(nameof(FirewallRule.LocalPorts), rule.LocalPortsDisplay),
+ new XElement(nameof(FirewallRule.RemotePorts), rule.RemotePortsDisplay),
+ new XElement(nameof(FirewallRule.LocalAddresses), rule.LocalAddressesDisplay),
+ new XElement(nameof(FirewallRule.RemoteAddresses), rule.RemoteAddressesDisplay),
+ new XElement(nameof(FirewallRule.NetworkProfiles), rule.NetworkProfilesDisplay),
+ new XElement(nameof(FirewallRule.InterfaceType), rule.InterfaceType),
+ new XElement(nameof(FirewallRule.Program), rule.ProgramDisplay),
+ new XElement(nameof(FirewallRule.Description), rule.Description)))));
+
+ document.Save(filePath);
+ }
+
+ ///
+ /// Creates a JSON file from the given collection.
+ ///
+ private static void CreateJson(IReadOnlyList collection, string filePath)
+ {
+ var jsonData = new object[collection.Count];
+
+ for (var i = 0; i < collection.Count; i++)
+ {
+ var rule = collection[i];
+ jsonData[i] = new
+ {
+ rule.Name,
+ rule.IsEnabled,
+ Direction = rule.Direction.ToString(),
+ Action = rule.Action.ToString(),
+ Protocol = rule.Protocol.ToString(),
+ LocalPorts = rule.LocalPortsDisplay,
+ RemotePorts = rule.RemotePortsDisplay,
+ LocalAddresses = rule.LocalAddressesDisplay,
+ RemoteAddresses = rule.RemoteAddressesDisplay,
+ NetworkProfiles = rule.NetworkProfilesDisplay,
+ InterfaceType = rule.InterfaceType.ToString(),
+ Program = rule.ProgramDisplay,
+ rule.Description
+ };
+ }
+
+ File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented));
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
index 61260da4e5..ac6442df1e 100644
--- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
+++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
@@ -234,6 +234,9 @@ public static class GlobalStaticConfiguration
// Application: Hosts File Editor
public static ExportFileType HostsFileEditor_ExportFileType => ExportFileType.Csv;
+ // Application: Firewall
+ public static ExportFileType Firewall_ExportFileType => ExportFileType.Csv;
+
// Application: Discovery Protocol
public static DiscoveryProtocol DiscoveryProtocol_Protocol => DiscoveryProtocol.LldpCdp;
public static int DiscoveryProtocol_Duration => 60;
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index bbe4f5a968..930fd23061 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -3255,6 +3255,32 @@ public double Firewall_ProfileWidth
}
} = GlobalStaticConfiguration.Profile_DefaultWidthExpanded;
+ public string Firewall_ExportFilePath
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public ExportFileType Firewall_ExportFileType
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = GlobalStaticConfiguration.Firewall_ExportFileType;
+
#endregion
#region Discovery Protocol
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index 29fec76905..4a5173afe6 100644
--- a/Source/NETworkManager/ViewModels/FirewallViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
@@ -1,25 +1,29 @@
-using System.Collections;
-using System.Threading.Tasks;
-using log4net;
+using log4net;
+using MahApps.Metro.Controls;
+using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
+using NETworkManager.Models.Export;
using NETworkManager.Models.Firewall;
-
-namespace NETworkManager.ViewModels;
-
-using System.Windows;
-using System.Windows.Data;
-using System.Windows.Threading;
+using NETworkManager.Settings;
+using NETworkManager.Utilities;
+using NETworkManager.Views;
+using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
-using System;
using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
using System.Windows.Input;
+using System.Windows.Threading;
+
+namespace NETworkManager.ViewModels;
+
using Controls;
using Profiles;
using Models;
-using Settings;
-using Utilities;
///
/// ViewModel for the Firewall application.
@@ -463,11 +467,50 @@ private void DeleteEntryAction()
}
/// Gets the command to export the firewall rules.
- public ICommand ExportCommand => new RelayCommand(_ => ExportAction());
+ public ICommand ExportCommand => new RelayCommand(_ => ExportAction().ConfigureAwait(false));
- private void ExportAction()
+ private Task ExportAction()
{
- // TODO: implement export
+ var childWindow = new ExportChildWindow();
+
+ var childWindowViewModel = new ExportViewModel(async instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+
+ try
+ {
+ ExportManager.Export(instance.FilePath, instance.FileType,
+ instance.ExportAll
+ ? Results
+ : new ObservableCollection(SelectedResults.Cast().ToArray()));
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while exporting data as " + instance.FileType, ex);
+
+ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Error,
+ Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine +
+ Environment.NewLine + ex.Message, ChildWindowIcon.Error);
+ }
+
+ SettingsManager.Current.Firewall_ExportFileType = instance.FileType;
+ SettingsManager.Current.Firewall_ExportFilePath = instance.FilePath;
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ }, [
+ ExportFileType.Csv, ExportFileType.Xml, ExportFileType.Json
+ ], true, SettingsManager.Current.Firewall_ExportFileType,
+ SettingsManager.Current.Firewall_ExportFilePath);
+
+ childWindow.Title = Strings.Export;
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
+
+ return Application.Current.MainWindow.ShowChildWindowAsync(childWindow);
}
///
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index f0bace57df..db23e12392 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -25,6 +25,7 @@
+
@@ -235,7 +236,7 @@
VerticalAlignment="Center" />
-
+
@@ -455,7 +456,7 @@
Text="{x:Static localization:Strings.NetworkProfiles}"
Style="{StaticResource InfoTextBlock}" />
+ Text="{Binding (firewall:FirewallRule.NetworkProfiles), Mode=OneWay, Converter={StaticResource FirewallNetworkProfilesToStringConverter}}" />
Date: Sat, 11 Apr 2026 06:06:33 +0200
Subject: [PATCH 06/23] Feature: Add network profile to networkinterfaceview
---
.../NetworkProfileToStringConverter.cs | 42 ++++++++++
.../Resources/Strings.Designer.cs | 9 +++
.../Resources/Strings.resx | 3 +
.../Network/NetworkInterface.cs | 37 ++++++++-
.../Network/NetworkInterfaceInfo.cs | 6 +-
.../Network/NetworkProfiles.cs | 2 +-
.../Views/NetworkInterfaceView.xaml | 78 ++++++++++++-------
7 files changed, 142 insertions(+), 35 deletions(-)
create mode 100644 Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs
diff --git a/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs b/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs
new file mode 100644
index 0000000000..841e3e4177
--- /dev/null
+++ b/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization.Resources;
+using NETworkManager.Models.Network;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert to a localized or vice versa.
+///
+public sealed class NetworkProfileToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to a localized .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Localized representing the network profile.
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not NetworkProfile profile
+ ? "-/-"
+ : profile switch
+ {
+ NetworkProfile.Domain => Strings.Domain,
+ NetworkProfile.Private => Strings.Private,
+ NetworkProfile.Public => Strings.Public,
+ _ => "-/-"
+ };
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index d263245e07..e3a4e842f5 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -11571,6 +11571,15 @@ public static string Block {
}
}
+ ///
+ /// Looks up a localized string similar to Network profile.
+ ///
+ public static string NetworkProfile {
+ get {
+ return ResourceManager.GetString("NetworkProfile", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Network profiles.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 1fea3de8ec..21fc7e296c 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -4043,6 +4043,9 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Block
+
+ Network profile
+
Network profiles
diff --git a/Source/NETworkManager.Models/Network/NetworkInterface.cs b/Source/NETworkManager.Models/Network/NetworkInterface.cs
index 6fbc000e2b..ae641531d2 100644
--- a/Source/NETworkManager.Models/Network/NetworkInterface.cs
+++ b/Source/NETworkManager.Models/Network/NetworkInterface.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Win32;
using NETworkManager.Utilities;
+using SMA = System.Management.Automation;
namespace NETworkManager.Models.Network;
@@ -72,6 +73,37 @@ public static List GetNetworkInterfaces()
{
List listNetworkInterfaceInfo = [];
+ // Query network profiles (Domain/Private/Public) for all connected interfaces via PowerShell.
+ // Keyed by InterfaceAlias which matches networkInterface.Name in the .NET API.
+ var profileByAlias = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ try
+ {
+ using var ps = SMA.PowerShell.Create();
+ ps.AddScript("Get-NetConnectionProfile | Select-Object InterfaceAlias, NetworkCategory");
+
+ foreach (var result in ps.Invoke())
+ {
+ var alias = result.Properties["InterfaceAlias"]?.Value?.ToString();
+ var category = result.Properties["NetworkCategory"]?.Value?.ToString();
+
+ if (string.IsNullOrEmpty(alias))
+ continue;
+
+ profileByAlias[alias] = category switch
+ {
+ "DomainAuthenticated" => NetworkProfile.Domain,
+ "Private" => NetworkProfile.Private,
+ "Public" => NetworkProfile.Public,
+ _ => NetworkProfile.NotConfigured
+ };
+ }
+ }
+ catch
+ {
+ // Profile lookup is best-effort; proceed without profile information on error.
+ }
+
foreach (var networkInterface in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces())
{
// NetworkInterfaceType 53 is proprietary virtual/internal interface
@@ -194,7 +226,10 @@ public static List GetNetworkInterfaces()
IPv6Gateway = [.. listIPv6Gateway],
DNSAutoconfigurationEnabled = dnsAutoconfigurationEnabled,
DNSSuffix = ipProperties.DnsSuffix,
- DNSServer = [.. ipProperties.DnsAddresses]
+ DNSServer = [.. ipProperties.DnsAddresses],
+ Profile = profileByAlias.TryGetValue(networkInterface.Name, out var profile)
+ ? profile
+ : NetworkProfile.NotConfigured
});
}
diff --git a/Source/NETworkManager.Models/Network/NetworkInterfaceInfo.cs b/Source/NETworkManager.Models/Network/NetworkInterfaceInfo.cs
index ced21944f1..f5130f49a5 100644
--- a/Source/NETworkManager.Models/Network/NetworkInterfaceInfo.cs
+++ b/Source/NETworkManager.Models/Network/NetworkInterfaceInfo.cs
@@ -120,8 +120,8 @@ public class NetworkInterfaceInfo
public IPAddress[] DNSServer { get; set; }
///
- /// Firewall network category (Private, Public, Domain)
+ /// Network category assigned by Windows (Domain, Private, Public).
+ /// when the interface has no active connection profile.
///
- // NOT IMPLEMENTED YET
- //public NetworkProfiles Profiles { get; set; }
+ public NetworkProfile Profile { get; set; } = NetworkProfile.NotConfigured;
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Network/NetworkProfiles.cs b/Source/NETworkManager.Models/Network/NetworkProfiles.cs
index 4d7d52d203..00dd4d9124 100644
--- a/Source/NETworkManager.Models/Network/NetworkProfiles.cs
+++ b/Source/NETworkManager.Models/Network/NetworkProfiles.cs
@@ -3,7 +3,7 @@
///
/// Defines the network profile detected by Windows.
///
-public enum NetworkProfiles
+public enum NetworkProfile
{
///
/// Network profile is not configured.
diff --git a/Source/NETworkManager/Views/NetworkInterfaceView.xaml b/Source/NETworkManager/Views/NetworkInterfaceView.xaml
index d65bc0e61a..2230bcfebb 100644
--- a/Source/NETworkManager/Views/NetworkInterfaceView.xaml
+++ b/Source/NETworkManager/Views/NetworkInterfaceView.xaml
@@ -37,6 +37,7 @@
x:Key="IPAddressSubnetmaskTupleArrayToStringConverter" />
+
@@ -222,8 +223,9 @@
+
-
@@ -252,6 +254,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -1527,7 +1545,7 @@
IsChecked="{Binding Path=ProfileFilterTagsMatchAll}"
Margin="10,0,0,0" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 0761db4368755e6b48928942a4038f0d52c9039b Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 02:21:09 +0200
Subject: [PATCH 09/23] Chore: Refactor the UI
---
.../Firewall/FirewallRule.cs | 6 +-
Source/NETworkManager/Views/FirewallView.xaml | 105 ++++++------------
.../NETworkManager/Views/IPScannerView.xaml | 15 ++-
3 files changed, 44 insertions(+), 82 deletions(-)
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRule.cs b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
index 1af34b849e..91c373b3e3 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallRule.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
@@ -163,11 +163,11 @@ public FirewallRule()
/// Local addresses as a human-readable string (e.g. "192.168.1.0/24; LocalSubnet"). Returns null when unrestricted.
public string LocalAddressesDisplay => LocalAddresses.Count == 0 ? null : string.Join("; ", LocalAddresses);
- /// Remote addresses as a human-readable string (e.g. "10.0.0.0/8"). Returns null when unrestricted.
- public string RemoteAddressesDisplay => RemoteAddresses.Count == 0 ? null : string.Join("; ", RemoteAddresses);
-
/// Local ports as a human-readable string (e.g. "80; 443; 8080-8090"). Returns null when unrestricted.
public string LocalPortsDisplay => LocalPorts.Count == 0 ? null : PortsToString(LocalPorts);
+
+ /// Remote addresses as a human-readable string (e.g. "10.0.0.0/8"). Returns null when unrestricted.
+ public string RemoteAddressesDisplay => RemoteAddresses.Count == 0 ? null : string.Join("; ", RemoteAddresses);
/// Remote ports as a human-readable string (e.g. "80; 443"). Returns null when unrestricted.
public string RemotePortsDisplay => RemotePorts.Count == 0 ? null : PortsToString(RemotePorts);
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index b36cce03ab..d86a375039 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -216,7 +216,7 @@
+ MinWidth="150" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
@@ -384,84 +408,17 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Source/NETworkManager/Views/IPScannerView.xaml b/Source/NETworkManager/Views/IPScannerView.xaml
index 93591d34e8..5ec9d99894 100644
--- a/Source/NETworkManager/Views/IPScannerView.xaml
+++ b/Source/NETworkManager/Views/IPScannerView.xaml
@@ -606,7 +606,8 @@
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
@@ -696,7 +697,8 @@
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
Date: Sun, 19 Apr 2026 02:35:56 +0200
Subject: [PATCH 10/23] Feature: UI & Copy data
---
.../FirewallRuleActionToStringConverter.cs | 41 ++++++++++++
.../FirewallRuleDirectionToStringConverter.cs | 41 ++++++++++++
.../ResourceIdentifier.cs | 4 +-
.../Resources/Strings.resx | 12 ++++
Source/NETworkManager/Views/FirewallView.xaml | 65 +++++++++++++++++--
5 files changed, 157 insertions(+), 6 deletions(-)
create mode 100644 Source/NETworkManager.Converters/FirewallRuleActionToStringConverter.cs
create mode 100644 Source/NETworkManager.Converters/FirewallRuleDirectionToStringConverter.cs
diff --git a/Source/NETworkManager.Converters/FirewallRuleActionToStringConverter.cs b/Source/NETworkManager.Converters/FirewallRuleActionToStringConverter.cs
new file mode 100644
index 0000000000..052932ab47
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallRuleActionToStringConverter.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization;
+using NETworkManager.Models.Firewall;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert to translated or vice versa.
+///
+public sealed class FirewallRuleActionToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to translated .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Translated .
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not FirewallRuleAction action
+ ? "-/-"
+ : ResourceTranslator.Translate(ResourceIdentifier.FirewallRuleAction, action);
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Converters/FirewallRuleDirectionToStringConverter.cs b/Source/NETworkManager.Converters/FirewallRuleDirectionToStringConverter.cs
new file mode 100644
index 0000000000..a180ff65c7
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallRuleDirectionToStringConverter.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization;
+using NETworkManager.Models.Firewall;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert to translated or vice versa.
+///
+public sealed class FirewallRuleDirectionToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to translated .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Translated .
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not FirewallRuleDirection direction
+ ? "-/-"
+ : ResourceTranslator.Translate(ResourceIdentifier.FirewallRuleDirection, direction);
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Localization/ResourceIdentifier.cs b/Source/NETworkManager.Localization/ResourceIdentifier.cs
index 803af9f1ec..1f36bc0209 100644
--- a/Source/NETworkManager.Localization/ResourceIdentifier.cs
+++ b/Source/NETworkManager.Localization/ResourceIdentifier.cs
@@ -24,5 +24,7 @@ public enum ResourceIdentifier
TimeUnit,
WiFiConnectionStatus,
FirewallProtocol,
- FirewallInterfaceType
+ FirewallInterfaceType,
+ FirewallRuleDirection,
+ FirewallRuleAction
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 21fc7e296c..8c83030909 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -4112,4 +4112,16 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Remote access
+
+ Inbound
+
+
+ Outbound
+
+
+ Block
+
+
+ Allow
+
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index d86a375039..6bbc728f7b 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -21,9 +21,12 @@
+
+
+
@@ -142,6 +145,61 @@
+
@@ -231,8 +289,7 @@
-
@@ -242,7 +299,6 @@
-
@@ -280,12 +336,11 @@
-
From 57a286375b1873a16e6948e066b48a5d8b65f50b Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 03:03:13 +0200
Subject: [PATCH 11/23] Chore: UI redesign
---
.../Resources/Strings.Designer.cs | 11 +-
.../Resources/Strings.resx | 3 +
.../ViewModels/DiscoveryProtocolViewModel.cs | 4 +-
.../ViewModels/FirewallViewModel.cs | 33 +++++-
.../Views/DiscoveryProtocolView.xaml | 110 +++++++++---------
Source/NETworkManager/Views/FirewallView.xaml | 38 ++++++
.../Views/HostsFileEditorView.xaml | 4 +
7 files changed, 141 insertions(+), 62 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index e3a4e842f5..4910ff7b09 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -4128,7 +4128,16 @@ public static string Firewall {
return ResourceManager.GetString("Firewall", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to Read-only mode. Modifying firewall rules requires elevated rights!
+ ///
+ public static string FirewallAdminMessage {
+ get {
+ return ResourceManager.GetString("FirewallAdminMessage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Firewall rules.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 8c83030909..294e6294d2 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -4112,6 +4112,9 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Remote access
+
+ Read-only mode. Modifying firewall rules requires elevated rights!
+
Inbound
diff --git a/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs b/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
index 6b57463e7d..7b917a8e3d 100644
--- a/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
@@ -295,7 +295,9 @@ private async Task RestartAsAdminAction()
///
/// Gets the command to start the capture.
///
- public ICommand CaptureCommand => new RelayCommand(_ => CaptureAction().ConfigureAwait(false));
+ public ICommand CaptureCommand => new RelayCommand(_ => CaptureAction().ConfigureAwait(false), Capture_CanExecute);
+
+ private bool Capture_CanExecute(object _) => ConfigurationManager.Current.IsAdmin && !IsCapturing;
///
/// Action to start the capture.
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index 4a5173afe6..5b83355817 100644
--- a/Source/NETworkManager/ViewModels/FirewallViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
@@ -435,7 +435,7 @@ private void AddEntryAction()
}
/// Gets the command to enable the selected firewall entry.
- public ICommand EnableEntryCommand => new RelayCommand(_ => EnableEntryAction(), _ => SelectedResult is { IsEnabled: false });
+ public ICommand EnableEntryCommand => new RelayCommand(_ => EnableEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult is { IsEnabled: false });
private void EnableEntryAction()
{
@@ -443,7 +443,7 @@ private void EnableEntryAction()
}
/// Gets the command to disable the selected firewall entry.
- public ICommand DisableEntryCommand => new RelayCommand(_ => DisableEntryAction(), _ => SelectedResult is { IsEnabled: true });
+ public ICommand DisableEntryCommand => new RelayCommand(_ => DisableEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult is { IsEnabled: true });
private void DisableEntryAction()
{
@@ -451,7 +451,7 @@ private void DisableEntryAction()
}
/// Gets the command to edit the selected firewall entry.
- public ICommand EditEntryCommand => new RelayCommand(_ => EditEntryAction(), _ => SelectedResult != null);
+ public ICommand EditEntryCommand => new RelayCommand(_ => EditEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult != null);
private void EditEntryAction()
{
@@ -459,13 +459,38 @@ private void EditEntryAction()
}
/// Gets the command to delete the selected firewall entry.
- public ICommand DeleteEntryCommand => new RelayCommand(_ => DeleteEntryAction(), _ => SelectedResult != null);
+ public ICommand DeleteEntryCommand => new RelayCommand(_ => DeleteEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult != null);
private void DeleteEntryAction()
{
// TODO: confirm and call Firewall.DeleteRuleAsync
}
+ /// Checks if entry modification commands can be executed.
+ private static bool ModifyEntry_CanExecute()
+ {
+ return ConfigurationManager.Current.IsAdmin &&
+ Application.Current.MainWindow != null &&
+ !((MetroWindow)Application.Current.MainWindow).IsAnyDialogOpen &&
+ !ConfigurationManager.Current.IsChildWindowOpen;
+ }
+
+ /// Gets the command to restart the application as administrator.
+ public ICommand RestartAsAdminCommand => new RelayCommand(_ => RestartAsAdminAction().ConfigureAwait(false));
+
+ private async Task RestartAsAdminAction()
+ {
+ try
+ {
+ (Application.Current.MainWindow as MainWindow)?.RestartApplication(true);
+ }
+ catch (Exception ex)
+ {
+ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Error, ex.Message,
+ ChildWindowIcon.Error);
+ }
+ }
+
/// Gets the command to export the firewall rules.
public ICommand ExportCommand => new RelayCommand(_ => ExportAction().ConfigureAwait(false));
diff --git a/Source/NETworkManager/Views/DiscoveryProtocolView.xaml b/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
index 47de679080..ffe9eb0929 100644
--- a/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
+++ b/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
@@ -16,21 +16,24 @@
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -61,16 +64,8 @@
Text="{x:Static localization:Strings.DurationS}" />
-
-
-
-
+
@@ -267,41 +262,44 @@
Foreground="{DynamicResource MahApps.Brushes.Gray3}"
HorizontalAlignment="Center" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index 6bbc728f7b..91188936f5 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -50,6 +50,7 @@
+
@@ -547,6 +548,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/NETworkManager/Views/HostsFileEditorView.xaml b/Source/NETworkManager/Views/HostsFileEditorView.xaml
index afdd720a2b..d49ddab130 100644
--- a/Source/NETworkManager/Views/HostsFileEditorView.xaml
+++ b/Source/NETworkManager/Views/HostsFileEditorView.xaml
@@ -33,6 +33,7 @@
+
@@ -75,6 +76,7 @@
Style="{StaticResource ResourceKey=SearchTextBox}" />
+
+
@@ -253,6 +256,7 @@
+
Date: Sun, 19 Apr 2026 03:10:06 +0200
Subject: [PATCH 12/23] Docs: Discovery Protocol
---
Website/docs/application/discovery-protocol.md | 2 ++
Website/docs/changelog/next-release.md | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/Website/docs/application/discovery-protocol.md b/Website/docs/application/discovery-protocol.md
index 5a0891a27b..b88536f214 100644
--- a/Website/docs/application/discovery-protocol.md
+++ b/Website/docs/application/discovery-protocol.md
@@ -30,6 +30,8 @@ In addition, further actions can be performed using the buttons at the bottom ri
:::note
+With `F5` or `Enter` you can start capturing network packets.
+
Right-click on the result to copy the information.
:::
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index dec3d0776a..377d63316b 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -47,6 +47,11 @@ Release date: **xx.xx.2025**
- Added Network Profile (domain, private, public) information to the Network Interface details view, if available. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+**Discovery Protocol**
+
+- Added support for `F5` and `Enter` keys to start capturing network packets. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+- Redesigned the "restart as admin" note to be more compact and visually consistent. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+
## Bug Fixes
**Port Scanner**
From 00ed3051948d612c7808f0477b31af7e07e1b115 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 03:12:29 +0200
Subject: [PATCH 13/23] Update package.json
---
Website/package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Website/package.json b/Website/package.json
index 72f0db0480..67e8949c11 100644
--- a/Website/package.json
+++ b/Website/package.json
@@ -20,8 +20,8 @@
"@mdx-js/react": "^3.1.1",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.4.1",
- "react": "^19.2.5",
- "react-dom": "^19.2.4",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
"react-image-gallery": "^2.1.2"
},
"devDependencies": {
From aacad1a11588d16982b996cf1534210854093a55 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 03:19:57 +0200
Subject: [PATCH 14/23] Update yarn.lock
---
Website/yarn.lock | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/Website/yarn.lock b/Website/yarn.lock
index 7043038f9b..5be26c51d6 100644
--- a/Website/yarn.lock
+++ b/Website/yarn.lock
@@ -7347,10 +7347,10 @@ rc@1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-dom@^19.2.4:
- version "19.2.4"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.4.tgz#6fac6bd96f7db477d966c7ec17c1a2b1ad8e6591"
- integrity sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==
+react-dom@^19.2.0:
+ version "19.2.5"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.5.tgz#b8768b10837d0b8e9ca5b9e2d58dff3d880ea25e"
+ integrity sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==
dependencies:
scheduler "^0.27.0"
@@ -7434,7 +7434,7 @@ react-router@5.3.4, react-router@^5.3.4:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
-react@^19.2.5:
+react@^19.2.0:
version "19.2.5"
resolved "https://registry.yarnpkg.com/react/-/react-19.2.5.tgz#c888ab8b8ef33e2597fae8bdb2d77edbdb42858b"
integrity sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==
From 70b404e1f4cca1fa0586b72d94543b121a8aa455 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 21:50:52 +0200
Subject: [PATCH 15/23] Feature: Enable/Disable/Delete Firewall rule
---
.../Resources/Strings.Designer.cs | 11 +-
.../Resources/Strings.resx | 5 +
.../Firewall/Firewall.cs | 289 +++++++++++++-----
.../ViewModels/FirewallViewModel.cs | 136 +++++++--
.../ViewModels/HostsFileEditorViewModel.cs | 7 +-
5 files changed, 353 insertions(+), 95 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 4910ff7b09..63da4f3910 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -11615,7 +11615,16 @@ public static string FailedToLoadFirewallRulesMessage {
return ResourceManager.GetString("FailedToLoadFirewallRulesMessage", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to The selected firewall rule is permanently deleted: {0}.
+ ///
+ public static string DeleteFirewallRuleMessage {
+ get {
+ return ResourceManager.GetString("DeleteFirewallRuleMessage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to WPS.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 294e6294d2..51a6795863 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -4055,6 +4055,11 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Failed to load firewall rules. {0}
+
+ The selected firewall rule is permanently deleted:
+
+{0}
+
Any
diff --git a/Source/NETworkManager.Models/Firewall/Firewall.cs b/Source/NETworkManager.Models/Firewall/Firewall.cs
index 5222e34eda..70b9e9ccb2 100644
--- a/Source/NETworkManager.Models/Firewall/Firewall.cs
+++ b/Source/NETworkManager.Models/Firewall/Firewall.cs
@@ -1,47 +1,87 @@
-using System;
+using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using SMA = System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Threading;
using System.Threading.Tasks;
+using SMA = System.Management.Automation;
using log4net;
namespace NETworkManager.Models.Firewall;
///
-/// Represents a firewall configuration and management class that provides functionalities
-/// for applying and managing firewall rules based on a specified profile.
+/// Provides static methods to read and modify Windows Firewall rules via PowerShell.
+/// All operations share a single that is initialized once with
+/// the required execution policy and the NetSecurity module, reducing per-call overhead.
+/// A serializes access so the runspace is never used concurrently.
///
public class Firewall
{
#region Variables
///
- /// The Logger.
+ /// The logger for this class.
///
private static readonly ILog Log = LogManager.GetLogger(typeof(Firewall));
+ ///
+ /// Prefix applied to the DisplayName of every rule managed by NETworkManager.
+ /// Used to scope Get-NetFirewallRule queries to only our own rules.
+ ///
private const string RuleIdentifier = "NETworkManager_";
-
+
+ ///
+ /// Ensures that only one PowerShell pipeline runs on at a time.
+ ///
+ private static readonly SemaphoreSlim Lock = new(1, 1);
+
+ ///
+ /// Shared PowerShell runspace, initialized once in the static constructor with
+ /// Set-ExecutionPolicy Bypass and Import-Module NetSecurity.
+ ///
+ private static readonly Runspace SharedRunspace;
+
+ ///
+ /// Opens and runs the one-time initialization script
+ /// so that subsequent operations do not need to repeat the module import.
+ ///
+ static Firewall()
+ {
+ SharedRunspace = RunspaceFactory.CreateRunspace();
+ SharedRunspace.Open();
+
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+ ps.AddScript(@"
+Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
+Import-Module NetSecurity -ErrorAction Stop").Invoke();
+ }
+
#endregion
#region Methods
///
- /// Reads all Windows Firewall rules whose display name starts with
- /// and maps them to objects.
+ /// Retrieves all Windows Firewall rules whose display name starts with
+ /// and maps each one to a object.
+ /// PowerShell errors during the query are logged as warnings; errors for individual
+ /// rules are caught so a single malformed rule does not abort the entire load.
///
- /// A task that resolves to the list of matching firewall rules.
+ ///
+ /// A list of objects representing the matching rules.
+ ///
public static async Task> GetRulesAsync()
{
- return await Task.Run(() =>
+ await Lock.WaitAsync();
+ try
{
- var rules = new List();
+ return await Task.Run(() =>
+ {
+ var rules = new List();
- using var ps = SMA.PowerShell.Create();
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
- ps.AddScript($@"
-Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
-Import-Module NetSecurity -ErrorAction Stop
+ ps.AddScript($@"
Get-NetFirewallRule -DisplayName '{RuleIdentifier}*' | ForEach-Object {{
$rule = $_
$portFilter = $rule | Get-NetFirewallPortFilter
@@ -66,57 +106,138 @@ public static async Task> GetRulesAsync()
}}
}}");
- var results = ps.Invoke();
-
- if (ps.Streams.Error.Count > 0)
- {
- foreach (var error in ps.Streams.Error)
- Log.Warn($"PowerShell error: {error}");
- }
+ var results = ps.Invoke();
- foreach (var result in results)
- {
- try
+ if (ps.Streams.Error.Count > 0)
{
- var displayName = result.Properties["DisplayName"]?.Value?.ToString() ?? string.Empty;
-
- var rule = new FirewallRule
- {
- Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
- IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
- Name = displayName.StartsWith(RuleIdentifier, StringComparison.Ordinal)
- ? displayName[RuleIdentifier.Length..]
- : displayName,
- Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
- Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
- Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
- Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
- LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
- RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
- LocalAddresses = ParseAddresses(result.Properties["LocalAddress"]?.Value?.ToString()),
- RemoteAddresses = ParseAddresses(result.Properties["RemoteAddress"]?.Value?.ToString()),
- NetworkProfiles = ParseProfile(result.Properties["Profile"]?.Value?.ToString()),
- InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
- };
-
- var program = result.Properties["Program"]?.Value as string;
-
- if (!string.IsNullOrWhiteSpace(program) && !program.Equals("Any", StringComparison.OrdinalIgnoreCase))
- rule.Program = new FirewallRuleProgram(program);
-
- rules.Add(rule);
+ foreach (var error in ps.Streams.Error)
+ Log.Warn($"PowerShell error: {error}");
}
- catch (Exception ex)
+
+ foreach (var result in results)
{
- Log.Warn($"Failed to parse firewall rule: {ex.Message}");
+ try
+ {
+ var displayName = result.Properties["DisplayName"]?.Value?.ToString() ?? string.Empty;
+
+ var rule = new FirewallRule
+ {
+ Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
+ IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
+ Name = displayName.StartsWith(RuleIdentifier, StringComparison.Ordinal)
+ ? displayName[RuleIdentifier.Length..]
+ : displayName,
+ Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
+ Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
+ Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
+ Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
+ LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
+ RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
+ LocalAddresses = ParseAddresses(result.Properties["LocalAddress"]?.Value?.ToString()),
+ RemoteAddresses = ParseAddresses(result.Properties["RemoteAddress"]?.Value?.ToString()),
+ NetworkProfiles = ParseProfile(result.Properties["Profile"]?.Value?.ToString()),
+ InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
+ };
+
+ var program = result.Properties["Program"]?.Value as string;
+
+ if (!string.IsNullOrWhiteSpace(program) && !program.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ rule.Program = new FirewallRuleProgram(program);
+
+ rules.Add(rule);
+ }
+ catch (Exception ex)
+ {
+ Log.Warn($"Failed to parse firewall rule: {ex.Message}");
+ }
}
- }
- return rules;
- });
+ return rules;
+ });
+ }
+ finally
+ {
+ Lock.Release();
+ }
+ }
+
+ ///
+ /// Enables or disables the given by running
+ /// Enable-NetFirewallRule or Disable-NetFirewallRule against
+ /// the rule's internal .
+ ///
+ ///
+ /// The firewall rule to modify.
+ ///
+ ///
+ /// to enable the rule; to disable it.
+ ///
+ ///
+ /// Thrown when the PowerShell pipeline reports one or more errors.
+ ///
+ public static async Task SetRuleEnabledAsync(FirewallRule rule, bool enabled)
+ {
+ await Lock.WaitAsync();
+ try
+ {
+ await Task.Run(() =>
+ {
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+
+ ps.AddScript($@"{(enabled ? "Enable" : "Disable")}-NetFirewallRule -Name '{rule.Id}'");
+ ps.Invoke();
+
+ if (ps.Streams.Error.Count > 0)
+ throw new Exception(string.Join("; ", ps.Streams.Error));
+ });
+ }
+ finally
+ {
+ Lock.Release();
+ }
}
- /// Parses a PowerShell direction string to .
+ ///
+ /// Permanently removes the given by running
+ /// Remove-NetFirewallRule against the rule's internal .
+ ///
+ ///
+ /// The firewall rule to delete.
+ ///
+ ///
+ /// Thrown when the PowerShell pipeline reports one or more errors.
+ ///
+ public static async Task DeleteRuleAsync(FirewallRule rule)
+ {
+ await Lock.WaitAsync();
+ try
+ {
+ await Task.Run(() =>
+ {
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+
+ ps.AddScript($@"Remove-NetFirewallRule -Name '{rule.Id}'");
+ ps.Invoke();
+
+ if (ps.Streams.Error.Count > 0)
+ throw new Exception(string.Join("; ", ps.Streams.Error));
+ });
+ }
+ finally
+ {
+ Lock.Release();
+ }
+ }
+
+ ///
+ /// Parses a PowerShell direction string (e.g. "Outbound") to a
+ /// value. Defaults to .
+ ///
+ ///
+ /// The raw string value returned by PowerShell.
+ ///
private static FirewallRuleDirection ParseDirection(string value)
{
return value switch
@@ -126,7 +247,13 @@ private static FirewallRuleDirection ParseDirection(string value)
};
}
- /// Parses a PowerShell action string to .
+ ///
+ /// Parses a PowerShell action string (e.g. "Allow") to a
+ /// value. Defaults to .
+ ///
+ ///
+ /// The raw string value returned by PowerShell.
+ ///
private static FirewallRuleAction ParseAction(string value)
{
return value switch
@@ -136,7 +263,14 @@ private static FirewallRuleAction ParseAction(string value)
};
}
- /// Parses a PowerShell protocol string to .
+ ///
+ /// Parses a PowerShell protocol string (e.g. "TCP", "Any") to a
+ /// value. Numeric protocol numbers are also accepted.
+ /// Defaults to for unrecognized values.
+ ///
+ ///
+ /// The raw string value returned by PowerShell.
+ ///
private static FirewallProtocol ParseProtocol(string value)
{
if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
@@ -156,8 +290,12 @@ private static FirewallProtocol ParseProtocol(string value)
///
/// Parses a comma-separated port string (e.g. "80,443,8080-8090") to a list of
- /// objects. Returns an empty list for Any or blank input.
+ /// objects.
+ /// Returns an empty list when the value is blank or "Any".
///
+ ///
+ /// The raw comma-separated port string returned by PowerShell.
+ ///
private static List ParsePorts(string value)
{
var list = new List();
@@ -185,9 +323,13 @@ private static List ParsePorts(string value)
}
///
- /// Parses a PowerShell profile string (e.g. "Domain, Private") to a three-element boolean array
- /// in the order Domain, Private, Public.
+ /// Parses a PowerShell profile string (e.g. "Domain, Private") to a
+ /// three-element boolean array in the order Domain, Private, Public.
+ /// "Any" and "All" set all three entries to .
///
+ ///
+ /// The raw profile string returned by PowerShell.
+ ///
private static bool[] ParseProfile(string value)
{
var profiles = new bool[3];
@@ -216,9 +358,13 @@ private static bool[] ParseProfile(string value)
}
///
- /// Parses a comma-separated address string (e.g. "192.168.1.0/24,LocalSubnet") to a list of
- /// address strings. Returns an empty list for Any or blank input.
+ /// Parses a comma-separated address string (e.g. "192.168.1.0/24,LocalSubnet") to a
+ /// list of address strings.
+ /// Returns an empty list when the value is blank or "Any".
///
+ ///
+ /// The raw comma-separated address string returned by PowerShell.
+ ///
private static List ParseAddresses(string value)
{
if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
@@ -227,7 +373,13 @@ private static List ParseAddresses(string value)
return [.. value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
}
- /// Parses a PowerShell interface-type string to .
+ ///
+ /// Parses a PowerShell interface-type string (e.g. "Wired") to a
+ /// value. Defaults to .
+ ///
+ ///
+ /// The raw string value returned by PowerShell.
+ ///
private static FirewallInterfaceType ParseInterfaceType(string value)
{
return value switch
@@ -238,5 +390,6 @@ private static FirewallInterfaceType ParseInterfaceType(string value)
_ => FirewallInterfaceType.Any,
};
}
+
#endregion
-}
+}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index 5b83355817..38069460d4 100644
--- a/Source/NETworkManager/ViewModels/FirewallViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
@@ -419,54 +419,129 @@ private void LoadSettings()
#region ICommand & Actions
- /// Gets the command to refresh the firewall rules.
+ ///
+ /// Gets the command to refresh the list of firewall rules from the system.
+ /// Disabled while a refresh is already in progress.
+ ///
public ICommand RefreshCommand => new RelayCommand(_ => RefreshAction().ConfigureAwait(false), Refresh_CanExecute);
+ ///
+ /// Returns when no refresh is currently running.
+ ///
private bool Refresh_CanExecute(object _) => !IsRefreshing;
+ ///
+ /// Delegates to to reload the firewall rules.
+ ///
private async Task RefreshAction() => await Refresh();
- /// Gets the command to add a new firewall entry.
+ ///
+ /// Gets the command to open the dialog for adding a new firewall rule.
+ ///
public ICommand AddEntryCommand => new RelayCommand(_ => AddEntryAction());
+ ///
+ /// Opens the add-firewall-rule dialog.
+ ///
private void AddEntryAction()
{
// TODO: open AddFirewallRuleDialog
}
- /// Gets the command to enable the selected firewall entry.
- public ICommand EnableEntryCommand => new RelayCommand(_ => EnableEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult is { IsEnabled: false });
-
- private void EnableEntryAction()
- {
- // TODO: call Firewall.SetRuleEnabledAsync(SelectedResult, true)
- }
+ ///
+ /// Gets the command to enable the selected firewall rule.
+ /// Only executable when the rule is currently disabled and modification is allowed.
+ ///
+ public ICommand EnableEntryCommand => new RelayCommand(_ => SetRuleEnabled(SelectedResult, true).ConfigureAwait(false), _ => ModifyEntry_CanExecute() && SelectedResult is { IsEnabled: false });
- /// Gets the command to disable the selected firewall entry.
- public ICommand DisableEntryCommand => new RelayCommand(_ => DisableEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult is { IsEnabled: true });
+ ///
+ /// Gets the command to disable the selected firewall rule.
+ /// Only executable when the rule is currently enabled and modification is allowed.
+ ///
+ public ICommand DisableEntryCommand => new RelayCommand(_ => SetRuleEnabled(SelectedResult, false).ConfigureAwait(false), _ => ModifyEntry_CanExecute() && SelectedResult is { IsEnabled: true });
- private void DisableEntryAction()
+ ///
+ /// Enables or disables the given via PowerShell,
+ /// then reloads the rule list to reflect the updated state.
+ /// Any PowerShell error is written to the log and shown in the status bar.
+ ///
+ ///
+ /// The firewall rule to modify.
+ ///
+ ///
+ /// to enable the rule; to disable it.
+ ///
+ private async Task SetRuleEnabled(FirewallRule rule, bool enabled)
{
- // TODO: call Firewall.SetRuleEnabledAsync(SelectedResult, false)
+ try
+ {
+ await Firewall.SetRuleEnabledAsync(rule, enabled);
+ await Refresh();
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"Error while {(enabled ? "enabling" : "disabling")} firewall rule", ex);
+
+ StatusMessage = ex.Message;
+ IsStatusMessageDisplayed = true;
+ }
}
- /// Gets the command to edit the selected firewall entry.
+ ///
+ /// Gets the command to open the dialog for editing the selected firewall rule.
+ /// Only executable when a rule is selected and modification is allowed.
+ ///
public ICommand EditEntryCommand => new RelayCommand(_ => EditEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult != null);
+ ///
+ /// Opens the edit-firewall-rule dialog for the selected rule.
+ ///
private void EditEntryAction()
{
// TODO: open EditFirewallRuleDialog
}
- /// Gets the command to delete the selected firewall entry.
- public ICommand DeleteEntryCommand => new RelayCommand(_ => DeleteEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult != null);
+ ///
+ /// Gets the command to permanently delete the selected firewall rule.
+ /// Only executable when a rule is selected and modification is allowed.
+ ///
+ public ICommand DeleteEntryCommand => new RelayCommand(_ => DeleteEntry().ConfigureAwait(false), _ => ModifyEntry_CanExecute() && SelectedResult != null);
- private void DeleteEntryAction()
+ ///
+ /// Shows a confirmation dialog and, if confirmed, deletes the selected firewall rule
+ /// via PowerShell and reloads the rule list.
+ /// Any PowerShell error is written to the log and shown in the status bar.
+ ///
+ private async Task DeleteEntry()
{
- // TODO: confirm and call Firewall.DeleteRuleAsync
+ var result = await DialogHelper.ShowConfirmationMessageAsync(
+ Application.Current.MainWindow,
+ Strings.DeleteEntry,
+ string.Format(Strings.DeleteFirewallRuleMessage, SelectedResult.Name),
+ ChildWindowIcon.Info,
+ Strings.Delete);
+
+ if (!result)
+ return;
+
+ try
+ {
+ await Firewall.DeleteRuleAsync(SelectedResult);
+ await Refresh();
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while deleting firewall rule", ex);
+
+ StatusMessage = ex.Message;
+ IsStatusMessageDisplayed = true;
+ }
}
- /// Checks if entry modification commands can be executed.
+ ///
+ /// Returns when the application is running as administrator,
+ /// no dialog is open, and no child window is open — i.e. it is safe to modify a rule.
+ ///
private static bool ModifyEntry_CanExecute()
{
return ConfigurationManager.Current.IsAdmin &&
@@ -475,9 +550,14 @@ private static bool ModifyEntry_CanExecute()
!ConfigurationManager.Current.IsChildWindowOpen;
}
- /// Gets the command to restart the application as administrator.
+ ///
+ /// Gets the command to restart the application with administrator privileges.
+ ///
public ICommand RestartAsAdminCommand => new RelayCommand(_ => RestartAsAdminAction().ConfigureAwait(false));
+ ///
+ /// Restarts the application elevated. Shows an error dialog if the restart fails.
+ ///
private async Task RestartAsAdminAction()
{
try
@@ -491,9 +571,15 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
}
}
- /// Gets the command to export the firewall rules.
+ ///
+ /// Gets the command to export the current firewall rule list to a file.
+ ///
public ICommand ExportCommand => new RelayCommand(_ => ExportAction().ConfigureAwait(false));
+ ///
+ /// Opens the export child window and writes the selected or all firewall rules to the
+ /// chosen file format (CSV, XML, or JSON). Shows an error dialog if the export fails.
+ ///
private Task ExportAction()
{
var childWindow = new ExportChildWindow();
@@ -740,8 +826,14 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
#region Methods
///
- /// Loads firewall rules from Windows via PowerShell and populates .
+ /// Loads all NETworkManager firewall rules from the system via PowerShell and
+ /// replaces the contents of with the new list.
+ /// Updates throughout to reflect loading progress.
///
+ ///
+ /// When the initial UI delay is skipped so the first load
+ /// on startup feels immediate.
+ ///
private async Task Refresh(bool init = false)
{
if (IsRefreshing)
diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
index 2502a4da06..d90c511f71 100644
--- a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
+++ b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
@@ -454,14 +454,13 @@ private async Task DeleteEntryAction()
{
IsModifying = true;
- var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
+ var result = await DialogHelper.ShowConfirmationMessageAsync(
+ Application.Current.MainWindow,
Strings.DeleteEntry,
string.Format(Strings.DeleteHostsFileEntryMessage, SelectedResult.IPAddress, SelectedResult.Hostname,
string.IsNullOrEmpty(SelectedResult.Comment) ? "" : $"# {SelectedResult.Comment}"),
ChildWindowIcon.Info,
- Strings.Delete
- );
-
+ Strings.Delete);
if (!result)
{
From a144800d058033038b1cd5cffba7bf3b38155efd Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 21:57:13 +0200
Subject: [PATCH 16/23] Feature: Open Hosts File Editor
---
.../Resources/Strings.Designer.cs | 9 +++
.../Resources/Strings.resx | 3 +
.../HostsFileEditor/HostsFileEditor.cs | 2 +-
.../ViewModels/HostsFileEditorViewModel.cs | 22 ++++++
.../Views/HostsFileEditorView.xaml | 70 +++++++++++++------
Website/docs/changelog/next-release.md | 4 ++
6 files changed, 86 insertions(+), 24 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 63da4f3910..b181200e15 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -4822,6 +4822,15 @@ public static string HostsFileEditorAdminMessage {
return ResourceManager.GetString("HostsFileEditorAdminMessage", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Open hosts file.
+ ///
+ public static string OpenHostsFile {
+ get {
+ return ResourceManager.GetString("OpenHostsFile", resourceCulture);
+ }
+ }
///
/// Looks up a localized string similar to The entry was not found in the "hosts" file! Maybe the file has been modified..
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 51a6795863..669d98df65 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -3802,6 +3802,9 @@ Right-click for more options.
Read-only mode. Modifying the hosts file requires elevated rights!
+
+ Open hosts file
+
Comment
diff --git a/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs b/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs
index 82e1817247..2a211c638c 100644
--- a/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs
+++ b/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs
@@ -37,7 +37,7 @@ private static void OnHostsFileChanged()
///
/// Path to the hosts file.
///
- private static string HostsFilePath => Path.Combine(HostsFolderPath, "hosts");
+ public static string HostsFilePath => Path.Combine(HostsFolderPath, "hosts");
///
/// Identifier for the hosts file backup.
diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
index d90c511f71..3abb776105 100644
--- a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
+++ b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
@@ -530,6 +530,28 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
}
}
+ ///
+ /// Gets the command to open the hosts file with the system default editor.
+ ///
+ public ICommand OpenHostsFileCommand => new RelayCommand(_ => OpenHostsFileAction().ConfigureAwait(false));
+
+ ///
+ /// Opens the hosts file with the system default editor.
+ /// Shows an error dialog if the process cannot be started.
+ ///
+ private async Task OpenHostsFileAction()
+ {
+ try
+ {
+ ExternalProcessStarter.RunProcess(HostsFileEditor.HostsFilePath);
+ }
+ catch (Exception ex)
+ {
+ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Error, ex.Message,
+ ChildWindowIcon.Error);
+ }
+ }
+
#endregion
#region Methods
diff --git a/Source/NETworkManager/Views/HostsFileEditorView.xaml b/Source/NETworkManager/Views/HostsFileEditorView.xaml
index d49ddab130..3817baf340 100644
--- a/Source/NETworkManager/Views/HostsFileEditorView.xaml
+++ b/Source/NETworkManager/Views/HostsFileEditorView.xaml
@@ -30,7 +30,9 @@
-
+
+
+
@@ -213,30 +215,52 @@
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -250,14 +274,14 @@
-
-
-
-
+
+
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 377d63316b..9fe7865c2d 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -52,6 +52,10 @@ Release date: **xx.xx.2025**
- Added support for `F5` and `Enter` keys to start capturing network packets. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
- Redesigned the "restart as admin" note to be more compact and visually consistent. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+**Hosts File Editor**
+
+- Button to open the hosts file in the default text editor added. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+
## Bug Fixes
**Port Scanner**
From b92a304962716f0cff443b548e3132a99d6002a8 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 23:12:28 +0200
Subject: [PATCH 17/23] Feature: Firewall add / edit firewall rule
---
.../Resources/StaticStrings.Designer.cs | 18 +
.../Resources/StaticStrings.resx | 6 +
.../Resources/Strings.Designer.cs | 45 +++
.../Resources/Strings.resx | 15 +
.../Firewall/Firewall.cs | 138 +++++++
.../EmptyOrFirewallAddressValidator.cs | 46 +++
.../ViewModels/FirewallRuleViewModel.cs | 360 ++++++++++++++++++
.../ViewModels/FirewallViewModel.cs | 146 ++++++-
Source/NETworkManager/Views/ARPTableView.xaml | 16 +-
.../Views/FirewallRuleChildWindow.xaml | 278 ++++++++++++++
.../Views/FirewallRuleChildWindow.xaml.cs | 21 +
Source/NETworkManager/Views/FirewallView.xaml | 16 +-
.../Views/HostsFileEditorView.xaml | 17 +-
.../NETworkManager/Views/ListenersView.xaml | 16 +-
Website/docs/changelog/next-release.md | 4 +
15 files changed, 1109 insertions(+), 33 deletions(-)
create mode 100644 Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs
create mode 100644 Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs
create mode 100644 Source/NETworkManager/Views/FirewallRuleChildWindow.xaml
create mode 100644 Source/NETworkManager/Views/FirewallRuleChildWindow.xaml.cs
diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs b/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
index 35c35d1075..a968f48fed 100644
--- a/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
@@ -734,5 +734,23 @@ public static string XML {
return ResourceManager.GetString("XML", resourceCulture);
}
}
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die MyApp - HTTP ähnelt.
+ ///
+ public static string ExampleFirewallRuleName {
+ get {
+ return ResourceManager.GetString("ExampleFirewallRuleName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die 192.168.1.0/24; LocalSubnet ähnelt.
+ ///
+ public static string ExampleFirewallAddresses {
+ get {
+ return ResourceManager.GetString("ExampleFirewallAddresses", resourceCulture);
+ }
+ }
}
}
diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.resx b/Source/NETworkManager.Localization/Resources/StaticStrings.resx
index 957880db82..a2ed234c70 100644
--- a/Source/NETworkManager.Localization/Resources/StaticStrings.resx
+++ b/Source/NETworkManager.Localization/Resources/StaticStrings.resx
@@ -342,4 +342,10 @@
borntoberoot.net or 1.1.1.1
+
+ MyApp - HTTP
+
+
+ 192.168.1.0/24; LocalSubnet
+
\ No newline at end of file
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index b181200e15..8a56008b17 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -11768,5 +11768,50 @@ public static string ZipCode {
return ResourceManager.GetString("ZipCode", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Local ports.
+ ///
+ public static string LocalPorts {
+ get {
+ return ResourceManager.GetString("LocalPorts", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remote ports.
+ ///
+ public static string RemotePorts {
+ get {
+ return ResourceManager.GetString("RemotePorts", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Local addresses.
+ ///
+ public static string LocalAddresses {
+ get {
+ return ResourceManager.GetString("LocalAddresses", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remote addresses.
+ ///
+ public static string RemoteAddresses {
+ get {
+ return ResourceManager.GetString("RemoteAddresses", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enter a valid IP address, subnet (e.g. 10.0.0.0/8) or keyword (e.g. LocalSubnet, Internet).
+ ///
+ public static string EnterValidFirewallAddress {
+ get {
+ return ResourceManager.GetString("EnterValidFirewallAddress", resourceCulture);
+ }
+ }
}
}
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 669d98df65..be263b510f 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -4135,4 +4135,19 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Allow
+
+ Local ports
+
+
+ Remote ports
+
+
+ Local addresses
+
+
+ Remote addresses
+
+
+ Enter a valid IP address, subnet (e.g. 10.0.0.0/8) or keyword (e.g. LocalSubnet, Internet)
+
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/Firewall.cs b/Source/NETworkManager.Models/Firewall/Firewall.cs
index 70b9e9ccb2..58ef612239 100644
--- a/Source/NETworkManager.Models/Firewall/Firewall.cs
+++ b/Source/NETworkManager.Models/Firewall/Firewall.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Management.Automation.Runspaces;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SMA = System.Management.Automation;
@@ -231,6 +233,142 @@ await Task.Run(() =>
}
}
+ ///
+ /// Creates a new Windows Firewall rule with the properties specified in .
+ /// The rule's is prefixed with so
+ /// it is picked up by on the next refresh.
+ ///
+ ///
+ /// The firewall rule to create.
+ ///
+ ///
+ /// Thrown when the PowerShell pipeline reports one or more errors.
+ ///
+ public static async Task AddRuleAsync(FirewallRule rule)
+ {
+ await Lock.WaitAsync();
+ try
+ {
+ await Task.Run(() =>
+ {
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+
+ ps.AddScript(BuildAddScript(rule));
+ ps.Invoke();
+
+ if (ps.Streams.Error.Count > 0)
+ throw new Exception(string.Join("; ", ps.Streams.Error));
+ });
+ }
+ finally
+ {
+ Lock.Release();
+ }
+ }
+
+ ///
+ /// Builds the PowerShell script that calls New-NetFirewallRule with all
+ /// properties from .
+ ///
+ ///
+ /// The firewall rule whose properties are used to build the script.
+ ///
+ private static string BuildAddScript(FirewallRule rule)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("$params = @{");
+ sb.AppendLine($" DisplayName = '{RuleIdentifier}{EscapePs(rule.Name)}'");
+ sb.AppendLine($" Enabled = '{(rule.IsEnabled ? "True" : "False")}'");
+ sb.AppendLine($" Direction = '{rule.Direction}'");
+ sb.AppendLine($" Action = '{rule.Action}'");
+ sb.AppendLine($" Protocol = '{GetProtocolString(rule.Protocol)}'");
+ sb.AppendLine($" InterfaceType = '{GetInterfaceTypeString(rule.InterfaceType)}'");
+ sb.AppendLine($" Profile = '{GetProfileString(rule.NetworkProfiles)}'");
+ sb.AppendLine("}");
+
+ if (!string.IsNullOrWhiteSpace(rule.Description))
+ sb.AppendLine($"$params['Description'] = '{EscapePs(rule.Description)}'");
+
+ if (rule.Protocol is FirewallProtocol.TCP or FirewallProtocol.UDP)
+ {
+ if (rule.LocalPorts.Count > 0)
+ sb.AppendLine($"$params['LocalPort'] = '{FirewallRule.PortsToString(rule.LocalPorts, ',', false)}'");
+
+ if (rule.RemotePorts.Count > 0)
+ sb.AppendLine($"$params['RemotePort'] = '{FirewallRule.PortsToString(rule.RemotePorts, ',', false)}'");
+ }
+
+ if (rule.LocalAddresses.Count > 0)
+ sb.AppendLine($"$params['LocalAddress'] = '{string.Join(',', rule.LocalAddresses.Select(EscapePs))}'");
+
+ if (rule.RemoteAddresses.Count > 0)
+ sb.AppendLine($"$params['RemoteAddress'] = '{string.Join(',', rule.RemoteAddresses.Select(EscapePs))}'");
+
+ if (rule.Program != null && !string.IsNullOrWhiteSpace(rule.Program.Name))
+ sb.AppendLine($"$params['Program'] = '{EscapePs(rule.Program.Name)}'");
+
+ sb.AppendLine("New-NetFirewallRule @params");
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Escapes a string for embedding inside a PowerShell single-quoted string by
+ /// doubling any single-quote characters.
+ ///
+ /// The raw string value to escape.
+ private static string EscapePs(string value) => value.Replace("'", "''");
+
+ ///
+ /// Maps a value to the string accepted by
+ /// New-NetFirewallRule -Protocol.
+ ///
+ /// The protocol to convert.
+ private static string GetProtocolString(FirewallProtocol protocol) => protocol switch
+ {
+ FirewallProtocol.Any => "Any",
+ FirewallProtocol.TCP => "TCP",
+ FirewallProtocol.UDP => "UDP",
+ FirewallProtocol.ICMPv4 => "ICMPv4",
+ FirewallProtocol.ICMPv6 => "ICMPv6",
+ FirewallProtocol.GRE => "GRE",
+ FirewallProtocol.L2TP => "L2TP",
+ _ => ((int)protocol).ToString()
+ };
+
+ ///
+ /// Maps a value to the string accepted by
+ /// New-NetFirewallRule -InterfaceType.
+ ///
+ /// The interface type to convert.
+ private static string GetInterfaceTypeString(FirewallInterfaceType interfaceType) => interfaceType switch
+ {
+ FirewallInterfaceType.Wired => "Wired",
+ FirewallInterfaceType.Wireless => "Wireless",
+ FirewallInterfaceType.RemoteAccess => "RemoteAccess",
+ _ => "Any"
+ };
+
+ ///
+ /// Converts the three-element network-profile boolean array (Domain, Private, Public)
+ /// to the comma-separated profile string accepted by New-NetFirewallRule -Profile.
+ /// All-false or all-true both map to "Any".
+ ///
+ /// Three-element boolean array (Domain=0, Private=1, Public=2).
+ private static string GetProfileString(bool[] profiles)
+ {
+ if (profiles == null || profiles.Length < 3 || profiles.All(p => p) || profiles.All(p => !p))
+ return "Any";
+
+ var parts = new List(3);
+ if (profiles[0]) parts.Add("Domain");
+ if (profiles[1]) parts.Add("Private");
+ if (profiles[2]) parts.Add("Public");
+
+ return parts.Count == 0 ? "Any" : string.Join(",", parts);
+ }
+
///
/// Parses a PowerShell direction string (e.g. "Outbound") to a
/// value. Defaults to .
diff --git a/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs b/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs
new file mode 100644
index 0000000000..6e528d5c51
--- /dev/null
+++ b/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Globalization;
+using System.Net;
+using System.Windows.Controls;
+using NETworkManager.Localization.Resources;
+
+namespace NETworkManager.Validators;
+
+///
+/// Validates that the input is empty (meaning "Any") or contains semicolon-separated
+/// valid IPv4/IPv6 addresses, CIDR subnets, or recognized Windows Firewall keywords
+/// (e.g. LocalSubnet, Internet, Intranet, DNS, DHCP, WINS, DefaultGateway).
+///
+public class EmptyOrFirewallAddressValidator : ValidationRule
+{
+ private static readonly string[] Keywords =
+ [
+ "Any", "LocalSubnet", "Internet", "Intranet", "DNS", "DHCP", "WINS", "DefaultGateway"
+ ];
+
+ ///
+ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
+ {
+ if (string.IsNullOrEmpty(value as string))
+ return ValidationResult.ValidResult;
+
+ foreach (var entry in ((string)value).Split(';'))
+ {
+ var token = entry.Trim();
+
+ if (string.IsNullOrEmpty(token))
+ continue;
+
+ if (Array.Exists(Keywords, k => k.Equals(token, StringComparison.OrdinalIgnoreCase)))
+ continue;
+
+ var slashIndex = token.IndexOf('/');
+ var addressPart = slashIndex > 0 ? token[..slashIndex] : token;
+
+ if (!IPAddress.TryParse(addressPart, out _))
+ return new ValidationResult(false, Strings.EnterValidFirewallAddress);
+ }
+
+ return ValidationResult.ValidResult;
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs b/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs
new file mode 100644
index 0000000000..de562b5984
--- /dev/null
+++ b/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs
@@ -0,0 +1,360 @@
+using NETworkManager.Models.Firewall;
+using NETworkManager.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Windows.Input;
+
+namespace NETworkManager.ViewModels;
+
+///
+/// ViewModel for adding or editing a firewall rule in the FirewallRule dialog.
+///
+public class FirewallRuleViewModel : ViewModelBase
+{
+ ///
+ /// Creates a new instance of for adding or
+ /// editing a firewall rule.
+ ///
+ /// OK command to save the rule.
+ /// Cancel command to discard changes.
+ /// Existing rule to edit; to add a new rule.
+ public FirewallRuleViewModel(Action okCommand,
+ Action cancelHandler, FirewallRule entry = null)
+ {
+ OKCommand = new RelayCommand(_ => okCommand(this));
+ CancelCommand = new RelayCommand(_ => cancelHandler(this));
+
+ Entry = entry;
+
+ if (entry == null)
+ {
+ IsEnabled = true;
+ Direction = FirewallRuleDirection.Inbound;
+ Action = FirewallRuleAction.Allow;
+ Protocol = FirewallProtocol.Any;
+ InterfaceType = FirewallInterfaceType.Any;
+ NetworkProfileDomain = true;
+ NetworkProfilePrivate = true;
+ NetworkProfilePublic = true;
+ }
+ else
+ {
+ Name = entry.Name;
+ IsEnabled = entry.IsEnabled;
+ Description = entry.Description ?? string.Empty;
+ Direction = entry.Direction;
+ Action = entry.Action;
+ Protocol = entry.Protocol;
+ LocalPorts = FirewallRule.PortsToString(entry.LocalPorts);
+ RemotePorts = FirewallRule.PortsToString(entry.RemotePorts);
+ LocalAddresses = entry.LocalAddresses.Count > 0 ? string.Join("; ", entry.LocalAddresses) : string.Empty;
+ RemoteAddresses = entry.RemoteAddresses.Count > 0 ? string.Join("; ", entry.RemoteAddresses) : string.Empty;
+ Program = entry.Program?.Name ?? string.Empty;
+ InterfaceType = entry.InterfaceType;
+ NetworkProfileDomain = entry.NetworkProfiles.Length > 0 && entry.NetworkProfiles[0];
+ NetworkProfilePrivate = entry.NetworkProfiles.Length > 1 && entry.NetworkProfiles[1];
+ NetworkProfilePublic = entry.NetworkProfiles.Length > 2 && entry.NetworkProfiles[2];
+ }
+ }
+
+ ///
+ /// OK command to save the rule.
+ ///
+ public ICommand OKCommand { get; }
+
+ ///
+ /// Cancel command to discard changes.
+ ///
+ public ICommand CancelCommand { get; }
+
+ ///
+ /// The original firewall rule being edited, or when adding a new rule.
+ ///
+ public FirewallRule Entry { get; }
+
+ ///
+ /// Protocols available in the protocol drop-down.
+ ///
+ public IEnumerable Protocols { get; } =
+ [
+ FirewallProtocol.Any,
+ FirewallProtocol.TCP,
+ FirewallProtocol.UDP,
+ FirewallProtocol.ICMPv4,
+ FirewallProtocol.ICMPv6,
+ FirewallProtocol.GRE,
+ FirewallProtocol.L2TP
+ ];
+
+ ///
+ /// Directions available in the direction drop-down.
+ ///
+ public IEnumerable Directions { get; } = Enum.GetValues();
+
+ ///
+ /// Actions available in the action drop-down.
+ ///
+ public IEnumerable Actions { get; } = Enum.GetValues();
+
+ ///
+ /// Interface types available in the interface type drop-down.
+ ///
+ public IEnumerable InterfaceTypes { get; } = Enum.GetValues();
+
+ ///
+ /// Human-readable display name of the rule (without the NETworkManager_ prefix).
+ ///
+ public string Name
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Indicates whether the rule is enabled.
+ ///
+ public bool IsEnabled
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Optional description of the rule.
+ ///
+ public string Description
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Traffic direction (Inbound or Outbound).
+ ///
+ public FirewallRuleDirection Direction
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Rule action (Allow or Block).
+ ///
+ public FirewallRuleAction Action
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Network protocol. When changed away from TCP/UDP, local and remote port fields are cleared.
+ ///
+ public FirewallProtocol Protocol
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+
+ if (value is not (FirewallProtocol.TCP or FirewallProtocol.UDP))
+ {
+ LocalPorts = string.Empty;
+ RemotePorts = string.Empty;
+ }
+
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(PortsVisible));
+ }
+ }
+
+ ///
+ /// when the current protocol supports port filtering (TCP or UDP).
+ ///
+ public bool PortsVisible => Protocol is FirewallProtocol.TCP or FirewallProtocol.UDP;
+
+ ///
+ /// Semicolon-separated local port numbers or ranges (e.g. "80; 443; 8080-8090").
+ /// Only relevant when is TCP or UDP.
+ ///
+ public string LocalPorts
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Semicolon-separated remote port numbers or ranges.
+ /// Only relevant when is TCP or UDP.
+ ///
+ public string RemotePorts
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Semicolon-separated local addresses (IPs, CIDR subnets, or keywords such as LocalSubnet).
+ /// Empty means "Any".
+ ///
+ public string LocalAddresses
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Semicolon-separated remote addresses.
+ /// Empty means "Any".
+ ///
+ public string RemoteAddresses
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Full path to the executable this rule applies to. Empty means "Any program".
+ ///
+ public string Program
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Network interface type filter.
+ ///
+ public FirewallInterfaceType InterfaceType
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Whether the rule applies to the Domain network profile.
+ ///
+ public bool NetworkProfileDomain
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Whether the rule applies to the Private network profile.
+ ///
+ public bool NetworkProfilePrivate
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Whether the rule applies to the Public network profile.
+ ///
+ public bool NetworkProfilePublic
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index 38069460d4..13ef72216e 100644
--- a/Source/NETworkManager/ViewModels/FirewallViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
@@ -437,15 +437,47 @@ private void LoadSettings()
///
/// Gets the command to open the dialog for adding a new firewall rule.
+ /// Only enabled when the application is running as administrator.
///
- public ICommand AddEntryCommand => new RelayCommand(_ => AddEntryAction());
+ public ICommand AddEntryCommand => new RelayCommand(_ => AddEntry().ConfigureAwait(false), _ => ModifyEntry_CanExecute());
///
- /// Opens the add-firewall-rule dialog.
+ /// Opens the add-firewall-rule dialog. On confirmation, creates the rule via PowerShell
+ /// and refreshes the rule list.
///
- private void AddEntryAction()
+ private async Task AddEntry()
{
- // TODO: open AddFirewallRuleDialog
+ var childWindow = new FirewallRuleChildWindow();
+
+ var childWindowViewModel = new FirewallRuleViewModel(async instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+
+ try
+ {
+ await Firewall.AddRuleAsync(BuildRule(instance));
+ await Refresh();
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while adding firewall rule", ex);
+
+ StatusMessage = ex.Message;
+ IsStatusMessageDisplayed = true;
+ }
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ });
+
+ childWindow.Title = Strings.AddEntry;
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
+
+ await Application.Current.MainWindow.ShowChildWindowAsync(childWindow);
}
///
@@ -491,14 +523,47 @@ private async Task SetRuleEnabled(FirewallRule rule, bool enabled)
/// Gets the command to open the dialog for editing the selected firewall rule.
/// Only executable when a rule is selected and modification is allowed.
///
- public ICommand EditEntryCommand => new RelayCommand(_ => EditEntryAction(), _ => ModifyEntry_CanExecute() && SelectedResult != null);
+ public ICommand EditEntryCommand => new RelayCommand(_ => EditEntry().ConfigureAwait(false), _ => ModifyEntry_CanExecute() && SelectedResult != null);
///
- /// Opens the edit-firewall-rule dialog for the selected rule.
+ /// Opens the edit-firewall-rule dialog pre-filled with the selected rule's properties.
+ /// On confirmation, deletes the old rule, creates the updated rule via PowerShell,
+ /// and refreshes the rule list.
///
- private void EditEntryAction()
+ private async Task EditEntry()
{
- // TODO: open EditFirewallRuleDialog
+ var childWindow = new FirewallRuleChildWindow();
+
+ var childWindowViewModel = new FirewallRuleViewModel(async instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+
+ try
+ {
+ await Firewall.DeleteRuleAsync(instance.Entry);
+ await Firewall.AddRuleAsync(BuildRule(instance));
+ await Refresh();
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while editing firewall rule", ex);
+
+ StatusMessage = ex.Message;
+ IsStatusMessageDisplayed = true;
+ }
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ }, SelectedResult);
+
+ childWindow.Title = Strings.EditEntry;
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
+
+ await Application.Current.MainWindow.ShowChildWindowAsync(childWindow);
}
///
@@ -872,6 +937,71 @@ private async Task Refresh(bool init = false)
IsRefreshing = false;
}
+ ///
+ /// Builds a from the values the user entered in the dialog.
+ ///
+ /// The dialog ViewModel containing the user's input.
+ private static FirewallRule BuildRule(FirewallRuleViewModel vm) => new()
+ {
+ Name = vm.Name,
+ IsEnabled = vm.IsEnabled,
+ Description = vm.Description ?? string.Empty,
+ Direction = vm.Direction,
+ Action = vm.Action,
+ Protocol = vm.Protocol,
+ LocalPorts = ParsePortsString(vm.LocalPorts),
+ RemotePorts = ParsePortsString(vm.RemotePorts),
+ LocalAddresses = ParseAddressesString(vm.LocalAddresses),
+ RemoteAddresses = ParseAddressesString(vm.RemoteAddresses),
+ Program = string.IsNullOrWhiteSpace(vm.Program) ? null : new FirewallRuleProgram(vm.Program),
+ InterfaceType = vm.InterfaceType,
+ NetworkProfiles = [vm.NetworkProfileDomain, vm.NetworkProfilePrivate, vm.NetworkProfilePublic]
+ };
+
+ ///
+ /// Parses a semicolon-separated port string (e.g. "80; 443; 8080-8090") into a
+ /// list of objects.
+ ///
+ /// The semicolon-separated port string from the dialog.
+ private static List ParsePortsString(string value)
+ {
+ var list = new List();
+
+ if (string.IsNullOrWhiteSpace(value))
+ return list;
+
+ foreach (var token in value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
+ {
+ var dash = token.IndexOf('-');
+
+ if (dash > 0 &&
+ int.TryParse(token[..dash], out var start) &&
+ int.TryParse(token[(dash + 1)..], out var end))
+ {
+ list.Add(new FirewallPortSpecification(start, end));
+ }
+ else if (int.TryParse(token, out var port))
+ {
+ list.Add(new FirewallPortSpecification(port));
+ }
+ }
+
+ return list;
+ }
+
+ ///
+ /// Parses a semicolon-separated address string (e.g. "192.168.1.0/24; LocalSubnet")
+ /// into a list of address strings.
+ ///
+ /// The semicolon-separated address string from the dialog.
+ private static List ParseAddressesString(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return [];
+
+ return [.. value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
+ }
+
///
/// Sets the IsExpanded property for all profile groups.
///
diff --git a/Source/NETworkManager/Views/ARPTableView.xaml b/Source/NETworkManager/Views/ARPTableView.xaml
index 797a97bc4e..d32fab70d1 100644
--- a/Source/NETworkManager/Views/ARPTableView.xaml
+++ b/Source/NETworkManager/Views/ARPTableView.xaml
@@ -186,19 +186,23 @@
MinWidth="100" />
-
-
+
+
+
+
+
-
-
+
diff --git a/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml
new file mode 100644
index 0000000000..c62896560c
--- /dev/null
+++ b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml
@@ -0,0 +1,278 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml.cs b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml.cs
new file mode 100644
index 0000000000..7e6b1d5685
--- /dev/null
+++ b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Windows;
+using System.Windows.Threading;
+
+namespace NETworkManager.Views;
+
+public partial class FirewallRuleChildWindow
+{
+ public FirewallRuleChildWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void ChildWindow_OnLoaded(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(delegate
+ {
+ TextBoxName.Focus();
+ }));
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index 91188936f5..8ff23b4bbe 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -483,19 +483,23 @@
-
-
+
+
+
+
+
-
-
+
diff --git a/Source/NETworkManager/Views/HostsFileEditorView.xaml b/Source/NETworkManager/Views/HostsFileEditorView.xaml
index 3817baf340..f70d7c1830 100644
--- a/Source/NETworkManager/Views/HostsFileEditorView.xaml
+++ b/Source/NETworkManager/Views/HostsFileEditorView.xaml
@@ -216,21 +216,24 @@
-
-
+
+
+
+
+
-
-
+
diff --git a/Source/NETworkManager/Views/ListenersView.xaml b/Source/NETworkManager/Views/ListenersView.xaml
index d64c2a8866..7882087b01 100644
--- a/Source/NETworkManager/Views/ListenersView.xaml
+++ b/Source/NETworkManager/Views/ListenersView.xaml
@@ -164,19 +164,23 @@
SortMemberPath="Port" MinWidth="100" />
-
-
+
+
+
+
+
-
-
+
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 9fe7865c2d..08b76c6bd2 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -29,6 +29,10 @@ Release date: **xx.xx.2025**
## What's new?
+**Firewall**
+
+- New feature to quickly add, edit, enable, disable and delete NETworkManager-owned firewall rules. Managed rules are prefixed with `NETworkManager_` in the Windows Firewall. (See the [documentation](https://borntoberoot.net/NETworkManager/docs/application/firewall) for more details) [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+
**PowerShell**
- DPI scaling is now applied correctly when NETworkManager is moved to a monitor with a different DPI scaling factor. The embedded PowerShell (conhost) window now rescales its font automatically using the Windows Console API (`AttachConsole` + `SetCurrentConsoleFontEx`), bypassing the OS limitation that prevents `WM_DPICHANGED` from being forwarded to cross-process child windows. [#3352](https://github.com/BornToBeRoot/NETworkManager/pull/3352)
From fa13f2922d0fa049e71cd1fd16b34b469ae829ae Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 23:13:36 +0200
Subject: [PATCH 18/23] Chore: Adjust strings
---
.../Resources/StaticStrings.Designer.cs | 2 +-
Source/NETworkManager.Localization/Resources/StaticStrings.resx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs b/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
index a968f48fed..533505c677 100644
--- a/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
@@ -745,7 +745,7 @@ public static string ExampleFirewallRuleName {
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die 192.168.1.0/24; LocalSubnet ähnelt.
+ /// Sucht eine lokalisierte Zeichenfolge, die 10.0.0.0/8; LocalSubnet ähnelt.
///
public static string ExampleFirewallAddresses {
get {
diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.resx b/Source/NETworkManager.Localization/Resources/StaticStrings.resx
index a2ed234c70..006de73953 100644
--- a/Source/NETworkManager.Localization/Resources/StaticStrings.resx
+++ b/Source/NETworkManager.Localization/Resources/StaticStrings.resx
@@ -346,6 +346,6 @@
MyApp - HTTP
- 192.168.1.0/24; LocalSubnet
+ 10.0.0.0/8; LocalSubnet
\ No newline at end of file
From 6761e733be0da5317f87321f44a81d307bff3653 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 23:42:15 +0200
Subject: [PATCH 19/23] Feature: Firewall edit/del + docs
---
Source/NETworkManager/Views/FirewallView.xaml | 4 ++
.../docs/application/discovery-protocol.md | 6 +++
Website/docs/application/firewall.md | 39 +++++++++++++++++++
Website/docs/application/hosts-file-editor.md | 10 ++++-
Website/docs/introduction.mdx | 1 +
5 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index 8ff23b4bbe..229248b289 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -101,6 +101,10 @@
SelectedItem="{Binding SelectedResult}"
SelectedItemsList="{Binding SelectedResults, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RowDetailsVisibilityMode="Collapsed">
+
+
+
+
Date: Thu, 23 Apr 2026 23:45:16 +0200
Subject: [PATCH 20/23] Fix: Copilot feedback
---
...irewallNetworkProfilesToStringConverter.cs | 11 +++---
.../DocumentationManager.cs | 1 -
.../Network/NetworkInterface.cs | 7 ++--
.../SettingsViewManager.cs | 4 ---
.../EmptyOrFirewallAddressValidator.cs | 12 ++++++-
.../ViewModels/FirewallSettingsViewModel.cs | 35 -------------------
.../ViewModels/SettingsViewModel.cs | 6 ----
.../Views/FirewallSettingsView.xaml | 13 -------
.../Views/FirewallSettingsView.xaml.cs | 17 ---------
Source/NETworkManager/Views/FirewallView.xaml | 4 +--
Website/docs/application/firewall.md | 2 +-
11 files changed, 26 insertions(+), 86 deletions(-)
delete mode 100644 Source/NETworkManager/ViewModels/FirewallSettingsViewModel.cs
delete mode 100644 Source/NETworkManager/Views/FirewallSettingsView.xaml
delete mode 100644 Source/NETworkManager/Views/FirewallSettingsView.xaml.cs
diff --git a/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs b/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
index 10ce69e7e3..5e5225bad9 100644
--- a/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
+++ b/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
@@ -14,25 +14,28 @@ public sealed class FirewallNetworkProfilesToStringConverter : IValueConverter
{
///
/// Convert a array (Domain, Private, Public) to a localized .
- /// Returns null when all three profiles are active so that a TargetNullValue binding
- /// can supply the translated "Any" label.
+ /// Returns the localized "Any" label when all three profiles are active or none are active,
+ /// since both cases are treated as "Any" by the Windows Firewall PowerShell layer.
///
/// A array with exactly three elements.
///
///
///
- /// Localized, comma-separated profile list (e.g. "Domain, Private, Public").
+ /// Localized, comma-separated profile list (e.g. "Domain, Private, Public"), or "Any".
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not bool[] { Length: 3 } profiles)
return "-/-";
+ if ((profiles[0] && profiles[1] && profiles[2]) || (!profiles[0] && !profiles[1] && !profiles[2]))
+ return Strings.Any;
+
var names = new List(3);
if (profiles[0]) names.Add(Strings.Domain);
if (profiles[1]) names.Add(Strings.Private);
if (profiles[2]) names.Add(Strings.Public);
- return names.Count == 0 ? "-" : string.Join(", ", names);
+ return string.Join(", ", names);
}
///
diff --git a/Source/NETworkManager.Documentation/DocumentationManager.cs b/Source/NETworkManager.Documentation/DocumentationManager.cs
index 9e0fa88860..3907c3b97b 100644
--- a/Source/NETworkManager.Documentation/DocumentationManager.cs
+++ b/Source/NETworkManager.Documentation/DocumentationManager.cs
@@ -267,7 +267,6 @@ public static DocumentationIdentifier GetIdentifierBySettingsName(SettingsName n
SettingsName.WebConsole => GetIdentifierByApplicationName(ApplicationName.WebConsole),
SettingsName.SNMP => GetIdentifierByApplicationName(ApplicationName.SNMP),
SettingsName.SNTPLookup => GetIdentifierByApplicationName(ApplicationName.SNTPLookup),
- SettingsName.Firewall => GetIdentifierByApplicationName(ApplicationName.Firewall),
SettingsName.WakeOnLAN => GetIdentifierByApplicationName(ApplicationName.WakeOnLAN),
SettingsName.BitCalculator => GetIdentifierByApplicationName(ApplicationName.BitCalculator),
_ => DocumentationIdentifier.Default
diff --git a/Source/NETworkManager.Models/Network/NetworkInterface.cs b/Source/NETworkManager.Models/Network/NetworkInterface.cs
index ae641531d2..aefd584134 100644
--- a/Source/NETworkManager.Models/Network/NetworkInterface.cs
+++ b/Source/NETworkManager.Models/Network/NetworkInterface.cs
@@ -6,6 +6,7 @@
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
+using log4net;
using Microsoft.Win32;
using NETworkManager.Utilities;
using SMA = System.Management.Automation;
@@ -32,6 +33,8 @@ public sealed class NetworkInterface
/// drivers, filters, or extensions attached to real network interfaces.
/// See: https://github.com/dotnet/runtime/issues/122751
///
+ private static readonly ILog Log = LogManager.GetLogger(typeof(NetworkInterface));
+
private static readonly List NetworkInterfaceFilteredPatterns =
[
"Hyper-V Virtual Switch Extension Filter",
@@ -99,9 +102,9 @@ public static List GetNetworkInterfaces()
};
}
}
- catch
+ catch (Exception ex)
{
- // Profile lookup is best-effort; proceed without profile information on error.
+ Log.Warn("Failed to query network connection profiles via Get-NetConnectionProfile.", ex);
}
foreach (var networkInterface in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces())
diff --git a/Source/NETworkManager.Settings/SettingsViewManager.cs b/Source/NETworkManager.Settings/SettingsViewManager.cs
index 480ccc89b6..81fb2f5e6b 100644
--- a/Source/NETworkManager.Settings/SettingsViewManager.cs
+++ b/Source/NETworkManager.Settings/SettingsViewManager.cs
@@ -72,10 +72,6 @@ public static class SettingsViewManager
SettingsGroup.Application),
new(SettingsName.SNTPLookup, ApplicationManager.GetIcon(ApplicationName.SNTPLookup),
SettingsGroup.Application),
- /*
- new(SettingsName.Firewall, ApplicationManager.GetIcon(ApplicationName.Firewall),
- SettingsGroup.Application),
- */
new(SettingsName.WakeOnLAN, ApplicationManager.GetIcon(ApplicationName.WakeOnLAN),
SettingsGroup.Application),
new(SettingsName.BitCalculator, ApplicationManager.GetIcon(ApplicationName.BitCalculator),
diff --git a/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs b/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs
index 6e528d5c51..c13ed9eaf1 100644
--- a/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs
+++ b/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Net;
+using System.Net.Sockets;
using System.Windows.Controls;
using NETworkManager.Localization.Resources;
@@ -37,8 +38,17 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
var slashIndex = token.IndexOf('/');
var addressPart = slashIndex > 0 ? token[..slashIndex] : token;
- if (!IPAddress.TryParse(addressPart, out _))
+ if (!IPAddress.TryParse(addressPart, out var ip))
return new ValidationResult(false, Strings.EnterValidFirewallAddress);
+
+ if (slashIndex > 0)
+ {
+ var prefixStr = token[(slashIndex + 1)..];
+ var maxPrefix = ip.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32;
+
+ if (!int.TryParse(prefixStr, out var prefix) || prefix < 0 || prefix > maxPrefix)
+ return new ValidationResult(false, Strings.EnterValidFirewallAddress);
+ }
}
return ValidationResult.ValidResult;
diff --git a/Source/NETworkManager/ViewModels/FirewallSettingsViewModel.cs b/Source/NETworkManager/ViewModels/FirewallSettingsViewModel.cs
deleted file mode 100644
index 37f018e832..0000000000
--- a/Source/NETworkManager/ViewModels/FirewallSettingsViewModel.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using NETworkManager.Settings;
-
-namespace NETworkManager.ViewModels;
-
-public class FirewallSettingsViewModel : ViewModelBase
-{
- #region Variables
- ///
- /// Indicates whether the view model is loading.
- ///
- private readonly bool _isLoading;
- #endregion
-
- #region Constructor, load settings
- ///
- /// Construct the view model and load settings.
- ///
- public FirewallSettingsViewModel()
- {
- _isLoading = true;
-
- LoadSettings();
-
- _isLoading = false;
- }
-
- ///
- /// Load the settings via .
- ///
- private void LoadSettings()
- {
-
- }
- #endregion
-}
diff --git a/Source/NETworkManager/ViewModels/SettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsViewModel.cs
index 2f0168fab7..36b73fd514 100644
--- a/Source/NETworkManager/ViewModels/SettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsViewModel.cs
@@ -134,7 +134,6 @@ public SettingsViewInfo SelectedSettingsView
private SNTPLookupSettingsView _sntpLookupSettingsView;
private WakeOnLANSettingsView _wakeOnLANSettingsView;
private BitCalculatorSettingsView _bitCalculatorSettingsView;
- private FirewallSettingsView _firewallSettingsView;
#endregion
@@ -336,11 +335,6 @@ private void ChangeSettingsContent(SettingsViewInfo settingsViewInfo)
SettingsContent = _bitCalculatorSettingsView;
break;
- case SettingsName.Firewall:
- _firewallSettingsView ??= new FirewallSettingsView();
-
- SettingsContent = _firewallSettingsView;
- break;
}
}
diff --git a/Source/NETworkManager/Views/FirewallSettingsView.xaml b/Source/NETworkManager/Views/FirewallSettingsView.xaml
deleted file mode 100644
index 0790a6eead..0000000000
--- a/Source/NETworkManager/Views/FirewallSettingsView.xaml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
diff --git a/Source/NETworkManager/Views/FirewallSettingsView.xaml.cs b/Source/NETworkManager/Views/FirewallSettingsView.xaml.cs
deleted file mode 100644
index 94fcc3445c..0000000000
--- a/Source/NETworkManager/Views/FirewallSettingsView.xaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using NETworkManager.ViewModels;
-
-namespace NETworkManager.Views;
-
-///
-/// View for the firewall settings.
-///
-public partial class FirewallSettingsView
-{
- private readonly FirewallSettingsViewModel _viewModel = new();
-
- public FirewallSettingsView()
- {
- InitializeComponent();
- DataContext = _viewModel;
- }
-}
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index 229248b289..1c37d4917a 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -322,7 +322,7 @@
-
+
@@ -345,7 +345,7 @@
Foreground="#b95353"
VerticalAlignment="Center" />
-
+
diff --git a/Website/docs/application/firewall.md b/Website/docs/application/firewall.md
index 429763b4b8..1692790754 100644
--- a/Website/docs/application/firewall.md
+++ b/Website/docs/application/firewall.md
@@ -22,7 +22,7 @@ Adding, editing, enabling, disabling, or deleting firewall rules requires admini
:::
-
+
:::note
From e8dafdd0a2aca07c29139859f8dad02ef860e354 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Fri, 24 Apr 2026 00:17:14 +0200
Subject: [PATCH 21/23] Fix: More firewall validations
---
.../NETworkManager.Models/Firewall/FirewallRule.cs | 2 +-
.../EmptyOrPortRangeValidator.cs | 3 ---
.../ViewModels/FirewallRuleViewModel.cs | 13 +++++++++++++
.../NETworkManager/ViewModels/FirewallViewModel.cs | 13 -------------
.../Views/FirewallRuleChildWindow.xaml | 5 ++++-
Source/NETworkManager/Views/FirewallView.xaml.cs | 8 +-------
6 files changed, 19 insertions(+), 25 deletions(-)
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRule.cs b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
index 91c373b3e3..c4cfaf59a6 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallRule.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
@@ -188,7 +188,7 @@ public string NetworkProfilesDisplay
if (NetworkProfiles.Length > 1 && NetworkProfiles[1]) names.Add("Private");
if (NetworkProfiles.Length > 2 && NetworkProfiles[2]) names.Add("Public");
- return names.Count == 0 ? "-" : string.Join(", ", names);
+ return names.Count == 0 ? "Any" : string.Join(", ", names);
}
}
diff --git a/Source/NETworkManager.Validators/EmptyOrPortRangeValidator.cs b/Source/NETworkManager.Validators/EmptyOrPortRangeValidator.cs
index df90512554..888a9e101b 100644
--- a/Source/NETworkManager.Validators/EmptyOrPortRangeValidator.cs
+++ b/Source/NETworkManager.Validators/EmptyOrPortRangeValidator.cs
@@ -13,9 +13,6 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
var isValid = true;
- if (value == null)
- return new ValidationResult(false, Strings.EnterValidPortOrPortRange);
-
foreach (var portOrRange in ((string)value).Replace(" ", "").Split(';'))
if (portOrRange.Contains('-'))
{
diff --git a/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs b/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs
index de562b5984..32fe089a06 100644
--- a/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs
@@ -323,6 +323,9 @@ public bool NetworkProfileDomain
field = value;
OnPropertyChanged();
+ OnPropertyChanged(nameof(IsNetworkProfileDomainEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePrivateEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePublicEnabled));
}
}
@@ -339,6 +342,9 @@ public bool NetworkProfilePrivate
field = value;
OnPropertyChanged();
+ OnPropertyChanged(nameof(IsNetworkProfileDomainEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePrivateEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePublicEnabled));
}
}
@@ -355,6 +361,13 @@ public bool NetworkProfilePublic
field = value;
OnPropertyChanged();
+ OnPropertyChanged(nameof(IsNetworkProfileDomainEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePrivateEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePublicEnabled));
}
}
+
+ public bool IsNetworkProfileDomainEnabled => NetworkProfilePrivate || NetworkProfilePublic;
+ public bool IsNetworkProfilePrivateEnabled => NetworkProfileDomain || NetworkProfilePublic;
+ public bool IsNetworkProfilePublicEnabled => NetworkProfileDomain || NetworkProfilePrivate;
}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index 13ef72216e..04ebd4160d 100644
--- a/Source/NETworkManager/ViewModels/FirewallViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
@@ -689,19 +689,6 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
return Application.Current.MainWindow.ShowChildWindowAsync(childWindow);
}
- ///
- /// Gets the command to apply the selected profile configuration.
- ///
- public ICommand ApplyProfileCommand => new RelayCommand(_ => ApplyProfileAction());
-
- ///
- /// Action to apply the selected profile configuration.
- ///
- private void ApplyProfileAction()
- {
- MessageBox.Show("Not implemented");
- }
-
///
/// Gets the command to add a new profile.
///
diff --git a/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml
index c62896560c..3b97546304 100644
--- a/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml
+++ b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml
@@ -232,12 +232,15 @@
+ IsChecked="{Binding NetworkProfilePublic}"
+ IsEnabled="{Binding IsNetworkProfilePublicEnabled}" />
diff --git a/Source/NETworkManager/Views/FirewallView.xaml.cs b/Source/NETworkManager/Views/FirewallView.xaml.cs
index 2793e4ad87..fd3af4f61a 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml.cs
+++ b/Source/NETworkManager/Views/FirewallView.xaml.cs
@@ -53,13 +53,7 @@ private void ExpandRowDetails_OnClick(object sender, RoutedEventArgs e)
break;
}
}
-
- private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
- {
- if (e.ChangedButton == MouseButton.Left)
- _viewModel.ApplyProfileCommand.Execute(null);
- }
-
+
///
/// Offload event for toggling view to the view model.
///
From bc4d8b30005a6332564c41d2c9297528c5a50a04 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Fri, 24 Apr 2026 00:19:03 +0200
Subject: [PATCH 22/23] dotnet format
---
.../NetworkProfileToStringConverter.cs | 6 +-
.../Firewall/Firewall.cs | 66 +++++++++----------
.../Firewall/FirewallInterfaceType.cs | 6 +-
.../Firewall/FirewallPortLocation.cs | 2 +-
.../Firewall/FirewallPortSpecification.cs | 2 +-
.../Firewall/FirewallRule.cs | 4 +-
.../Firewall/FirewallRuleAction.cs | 2 +-
.../Firewall/FirewallRuleProgram.cs | 5 +-
.../Network/DiscoveryProtocol.cs | 2 +-
.../Network/NetworkInterface.cs | 6 +-
Source/NETworkManager.Profiles/ProfileInfo.cs | 4 +-
Source/NETworkManager.Profiles/ProfileName.cs | 2 +-
.../ProfileViewManager.cs | 2 +-
.../GlobalStaticConfiguration.cs | 2 +-
.../NETworkManager.Settings/SettingsInfo.cs | 2 +-
.../NETworkManager.Settings/SettingsName.cs | 2 +-
.../SettingsViewManager.cs | 2 +-
.../ReloadAnimationHelper.cs | 10 +--
.../PowerShellHelper.cs | 6 +-
.../ViewModels/FirewallViewModel.cs | 50 +++++++-------
.../ViewModels/NetworkInterfaceViewModel.cs | 2 +-
.../ViewModels/ProfileViewModel.cs | 8 +--
.../ViewModels/SNTPLookupViewModel.cs | 2 +-
.../ViewModels/SettingsWindowViewModel.cs | 6 +-
.../NETworkManager/Views/FirewallView.xaml.cs | 2 +-
25 files changed, 102 insertions(+), 101 deletions(-)
diff --git a/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs b/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs
index 841e3e4177..fe16a6b95a 100644
--- a/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs
+++ b/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs
@@ -25,10 +25,10 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
? "-/-"
: profile switch
{
- NetworkProfile.Domain => Strings.Domain,
+ NetworkProfile.Domain => Strings.Domain,
NetworkProfile.Private => Strings.Private,
- NetworkProfile.Public => Strings.Public,
- _ => "-/-"
+ NetworkProfile.Public => Strings.Public,
+ _ => "-/-"
};
}
diff --git a/Source/NETworkManager.Models/Firewall/Firewall.cs b/Source/NETworkManager.Models/Firewall/Firewall.cs
index 58ef612239..4151d08be4 100644
--- a/Source/NETworkManager.Models/Firewall/Firewall.cs
+++ b/Source/NETworkManager.Models/Firewall/Firewall.cs
@@ -124,21 +124,21 @@ public static async Task> GetRulesAsync()
var rule = new FirewallRule
{
- Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
- IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
- Name = displayName.StartsWith(RuleIdentifier, StringComparison.Ordinal)
+ Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
+ IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
+ Name = displayName.StartsWith(RuleIdentifier, StringComparison.Ordinal)
? displayName[RuleIdentifier.Length..]
: displayName,
- Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
- Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
- Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
- Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
- LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
- RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
- LocalAddresses = ParseAddresses(result.Properties["LocalAddress"]?.Value?.ToString()),
+ Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
+ Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
+ Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
+ Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
+ LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
+ RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
+ LocalAddresses = ParseAddresses(result.Properties["LocalAddress"]?.Value?.ToString()),
RemoteAddresses = ParseAddresses(result.Properties["RemoteAddress"]?.Value?.ToString()),
NetworkProfiles = ParseProfile(result.Properties["Profile"]?.Value?.ToString()),
- InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
+ InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
};
var program = result.Properties["Program"]?.Value as string;
@@ -327,14 +327,14 @@ private static string BuildAddScript(FirewallRule rule)
/// The protocol to convert.
private static string GetProtocolString(FirewallProtocol protocol) => protocol switch
{
- FirewallProtocol.Any => "Any",
- FirewallProtocol.TCP => "TCP",
- FirewallProtocol.UDP => "UDP",
+ FirewallProtocol.Any => "Any",
+ FirewallProtocol.TCP => "TCP",
+ FirewallProtocol.UDP => "UDP",
FirewallProtocol.ICMPv4 => "ICMPv4",
FirewallProtocol.ICMPv6 => "ICMPv6",
- FirewallProtocol.GRE => "GRE",
- FirewallProtocol.L2TP => "L2TP",
- _ => ((int)protocol).ToString()
+ FirewallProtocol.GRE => "GRE",
+ FirewallProtocol.L2TP => "L2TP",
+ _ => ((int)protocol).ToString()
};
///
@@ -344,10 +344,10 @@ private static string BuildAddScript(FirewallRule rule)
/// The interface type to convert.
private static string GetInterfaceTypeString(FirewallInterfaceType interfaceType) => interfaceType switch
{
- FirewallInterfaceType.Wired => "Wired",
- FirewallInterfaceType.Wireless => "Wireless",
+ FirewallInterfaceType.Wired => "Wired",
+ FirewallInterfaceType.Wireless => "Wireless",
FirewallInterfaceType.RemoteAccess => "RemoteAccess",
- _ => "Any"
+ _ => "Any"
};
///
@@ -381,7 +381,7 @@ private static FirewallRuleDirection ParseDirection(string value)
return value switch
{
"Outbound" => FirewallRuleDirection.Outbound,
- _ => FirewallRuleDirection.Inbound,
+ _ => FirewallRuleDirection.Inbound,
};
}
@@ -397,7 +397,7 @@ private static FirewallRuleAction ParseAction(string value)
return value switch
{
"Allow" => FirewallRuleAction.Allow,
- _ => FirewallRuleAction.Block,
+ _ => FirewallRuleAction.Block,
};
}
@@ -416,12 +416,12 @@ private static FirewallProtocol ParseProtocol(string value)
return value.ToUpperInvariant() switch
{
- "TCP" => FirewallProtocol.TCP,
- "UDP" => FirewallProtocol.UDP,
- "ICMPV4" => FirewallProtocol.ICMPv4,
- "ICMPV6" => FirewallProtocol.ICMPv6,
- "GRE" => FirewallProtocol.GRE,
- "L2TP" => FirewallProtocol.L2TP,
+ "TCP" => FirewallProtocol.TCP,
+ "UDP" => FirewallProtocol.UDP,
+ "ICMPV4" => FirewallProtocol.ICMPv4,
+ "ICMPV6" => FirewallProtocol.ICMPv6,
+ "GRE" => FirewallProtocol.GRE,
+ "L2TP" => FirewallProtocol.L2TP,
_ => int.TryParse(value, out var proto) ? (FirewallProtocol)proto : FirewallProtocol.Any,
};
}
@@ -486,9 +486,9 @@ private static bool[] ParseProfile(string value)
{
switch (token)
{
- case "Domain": profiles[0] = true; break;
+ case "Domain": profiles[0] = true; break;
case "Private": profiles[1] = true; break;
- case "Public": profiles[2] = true; break;
+ case "Public": profiles[2] = true; break;
}
}
@@ -522,10 +522,10 @@ private static FirewallInterfaceType ParseInterfaceType(string value)
{
return value switch
{
- "Wired" => FirewallInterfaceType.Wired,
- "Wireless" => FirewallInterfaceType.Wireless,
+ "Wired" => FirewallInterfaceType.Wired,
+ "Wireless" => FirewallInterfaceType.Wireless,
"RemoteAccess" => FirewallInterfaceType.RemoteAccess,
- _ => FirewallInterfaceType.Any,
+ _ => FirewallInterfaceType.Any,
};
}
diff --git a/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs b/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
index 438291b6d9..c607dc3524 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
@@ -9,17 +9,17 @@ public enum FirewallInterfaceType
/// Any interface type.
///
Any = -1,
-
+
///
/// Wired interface types, e.g. Ethernet.
///
Wired,
-
+
///
/// Wireless interface types, e.g. Wi-Fi.
///
Wireless,
-
+
///
/// Remote interface types, e.g. VPN, L2TP, OpenVPN, etc.
///
diff --git a/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs b/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs
index 549cb35744..874cabec2e 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs
@@ -9,7 +9,7 @@ public enum FirewallPortLocation
/// Ports of local host.
///
LocalPorts,
-
+
///
/// Ports of remote host.
///
diff --git a/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs b/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs
index ae26b65d61..f15d818c57 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs
@@ -60,7 +60,7 @@ public override string ToString()
{
if (Start is 0)
return string.Empty;
-
+
return End is -1 or 0 ? $"{Start}" : $"{Start}-{End}";
}
}
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRule.cs b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
index c4cfaf59a6..bb3ea76d36 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallRule.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
@@ -151,7 +151,7 @@ public bool[] NetworkProfiles
///
public FirewallRule()
{
-
+
}
#endregion
@@ -165,7 +165,7 @@ public FirewallRule()
/// Local ports as a human-readable string (e.g. "80; 443; 8080-8090"). Returns null when unrestricted.
public string LocalPortsDisplay => LocalPorts.Count == 0 ? null : PortsToString(LocalPorts);
-
+
/// Remote addresses as a human-readable string (e.g. "10.0.0.0/8"). Returns null when unrestricted.
public string RemoteAddressesDisplay => RemoteAddresses.Count == 0 ? null : string.Join("; ", RemoteAddresses);
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs b/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs
index eaaa63cfc4..cbb9d91887 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs
@@ -14,7 +14,7 @@ public enum FirewallRuleAction
/// Represents the action to allow network traffic.
///
Allow,
-
+
// Unsupported for now
//AllowIPsec
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs b/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
index 12133b1344..46cb44d6c3 100644
--- a/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
@@ -16,7 +16,8 @@ public class FirewallRuleProgram : ICloneable
///
[JsonIgnore]
[XmlIgnore]
- public FileInfo Executable {
+ public FileInfo Executable
+ {
private set;
get
{
@@ -93,7 +94,7 @@ public object Clone()
{
return new FirewallRuleProgram();
}
-
+
}
#endregion
diff --git a/Source/NETworkManager.Models/Network/DiscoveryProtocol.cs b/Source/NETworkManager.Models/Network/DiscoveryProtocol.cs
index 3ff78c5fb8..81badd3f5c 100644
--- a/Source/NETworkManager.Models/Network/DiscoveryProtocol.cs
+++ b/Source/NETworkManager.Models/Network/DiscoveryProtocol.cs
@@ -100,7 +100,7 @@ public void CaptureAsync(int duration, DiscoveryProtocol protocol)
Task.Run(() =>
{
using var ps = System.Management.Automation.PowerShell.Create();
-
+
var typeParam = protocol != DiscoveryProtocol.LldpCdp ? $" -Type {protocol.ToString().ToUpper()}" : "";
ps.AddScript($@"
diff --git a/Source/NETworkManager.Models/Network/NetworkInterface.cs b/Source/NETworkManager.Models/Network/NetworkInterface.cs
index aefd584134..bca63e1205 100644
--- a/Source/NETworkManager.Models/Network/NetworkInterface.cs
+++ b/Source/NETworkManager.Models/Network/NetworkInterface.cs
@@ -96,9 +96,9 @@ public static List GetNetworkInterfaces()
profileByAlias[alias] = category switch
{
"DomainAuthenticated" => NetworkProfile.Domain,
- "Private" => NetworkProfile.Private,
- "Public" => NetworkProfile.Public,
- _ => NetworkProfile.NotConfigured
+ "Private" => NetworkProfile.Private,
+ "Public" => NetworkProfile.Public,
+ _ => NetworkProfile.NotConfigured
};
}
}
diff --git a/Source/NETworkManager.Profiles/ProfileInfo.cs b/Source/NETworkManager.Profiles/ProfileInfo.cs
index 26ed49397e..d2c2edf234 100644
--- a/Source/NETworkManager.Profiles/ProfileInfo.cs
+++ b/Source/NETworkManager.Profiles/ProfileInfo.cs
@@ -221,7 +221,7 @@ public ProfileInfo(ProfileInfo profile)
// Firewall
Firewall_Enabled = profile.Firewall_Enabled;
//Firewall_Rules = profile.Firewall_Rules;
-
+
// Wake on LAN
WakeOnLAN_Enabled = profile.WakeOnLAN_Enabled;
WakeOnLAN_MACAddress = profile.WakeOnLAN_MACAddress;
@@ -470,7 +470,7 @@ public ProfileInfo(ProfileInfo profile)
public bool Firewall_Enabled { get; set; }
//public List Firewall_Rules { get; set; }
-
+
public bool WakeOnLAN_Enabled { get; set; }
public string WakeOnLAN_MACAddress { get; set; }
public string WakeOnLAN_Broadcast { get; set; }
diff --git a/Source/NETworkManager.Profiles/ProfileName.cs b/Source/NETworkManager.Profiles/ProfileName.cs
index 0ded53ef0b..7db47b880f 100644
--- a/Source/NETworkManager.Profiles/ProfileName.cs
+++ b/Source/NETworkManager.Profiles/ProfileName.cs
@@ -18,5 +18,5 @@ public enum ProfileName
Firewall,
WakeOnLAN,
Whois,
- IPGeolocation,
+ IPGeolocation,
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Profiles/ProfileViewManager.cs b/Source/NETworkManager.Profiles/ProfileViewManager.cs
index 5c58170cba..606041e5b2 100644
--- a/Source/NETworkManager.Profiles/ProfileViewManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileViewManager.cs
@@ -59,6 +59,6 @@ public static class ProfileViewManager
new(ProfileName.Whois, ApplicationManager.GetIcon(ApplicationName.Whois),
ProfileGroup.Application),
new(ProfileName.IPGeolocation, ApplicationManager.GetIcon(ApplicationName.IPGeolocation),
- ProfileGroup.Application),
+ ProfileGroup.Application),
];
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
index ac6442df1e..51623e151f 100644
--- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
+++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
@@ -236,7 +236,7 @@ public static class GlobalStaticConfiguration
// Application: Firewall
public static ExportFileType Firewall_ExportFileType => ExportFileType.Csv;
-
+
// Application: Discovery Protocol
public static DiscoveryProtocol DiscoveryProtocol_Protocol => DiscoveryProtocol.LldpCdp;
public static int DiscoveryProtocol_Duration => 60;
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 930fd23061..7d0caae60e 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -3282,7 +3282,7 @@ public ExportFileType Firewall_ExportFileType
} = GlobalStaticConfiguration.Firewall_ExportFileType;
#endregion
-
+
#region Discovery Protocol
public DiscoveryProtocol DiscoveryProtocol_Protocol
diff --git a/Source/NETworkManager.Settings/SettingsName.cs b/Source/NETworkManager.Settings/SettingsName.cs
index 415c308718..949de02c75 100644
--- a/Source/NETworkManager.Settings/SettingsName.cs
+++ b/Source/NETworkManager.Settings/SettingsName.cs
@@ -28,5 +28,5 @@ public enum SettingsName
SNTPLookup,
Firewall,
WakeOnLAN,
- BitCalculator,
+ BitCalculator,
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Settings/SettingsViewManager.cs b/Source/NETworkManager.Settings/SettingsViewManager.cs
index 81fb2f5e6b..08950281ac 100644
--- a/Source/NETworkManager.Settings/SettingsViewManager.cs
+++ b/Source/NETworkManager.Settings/SettingsViewManager.cs
@@ -75,6 +75,6 @@ public static class SettingsViewManager
new(SettingsName.WakeOnLAN, ApplicationManager.GetIcon(ApplicationName.WakeOnLAN),
SettingsGroup.Application),
new(SettingsName.BitCalculator, ApplicationManager.GetIcon(ApplicationName.BitCalculator),
- SettingsGroup.Application),
+ SettingsGroup.Application),
];
}
diff --git a/Source/NETworkManager.Utilities.WPF/ReloadAnimationHelper.cs b/Source/NETworkManager.Utilities.WPF/ReloadAnimationHelper.cs
index bc77172974..28e6a31f31 100644
--- a/Source/NETworkManager.Utilities.WPF/ReloadAnimationHelper.cs
+++ b/Source/NETworkManager.Utilities.WPF/ReloadAnimationHelper.cs
@@ -23,21 +23,21 @@ public static bool GetIsReloading(UIElement element) =>
private static void OnIsReloadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- if (d is not Rectangle rect || e.NewValue is not bool isReloading)
+ if (d is not Rectangle rect || e.NewValue is not bool isReloading)
return;
-
+
if (isReloading)
{
var rotate = new RotateTransform
{
- CenterX = rect.Width / 2,
+ CenterX = rect.Width / 2,
CenterY = rect.Height / 2
};
-
+
rect.RenderTransform = rotate;
var animation = new DoubleAnimation(0, 720, new Duration(TimeSpan.FromSeconds(2)));
-
+
rotate.BeginAnimation(RotateTransform.AngleProperty, animation);
}
else
diff --git a/Source/NETworkManager.Utilities/PowerShellHelper.cs b/Source/NETworkManager.Utilities/PowerShellHelper.cs
index 2303719bd0..a888690556 100644
--- a/Source/NETworkManager.Utilities/PowerShellHelper.cs
+++ b/Source/NETworkManager.Utilities/PowerShellHelper.cs
@@ -38,9 +38,9 @@ public static void ExecuteCommand(string command, bool asAdmin = false, ProcessW
if (Powershell.Length + BaseOpts.Length + commandOpts.Length > 32767)
{
scriptPath = Path.Combine(Path.GetTempPath(), $"NETworkManager_{Guid.NewGuid()}.ps1");
-
+
File.WriteAllText(scriptPath, command);
-
+
commandOpts = $" -ExecutionPolicy Bypass -File \"{scriptPath}\"";
}
@@ -66,7 +66,7 @@ public static void ExecuteCommand(string command, bool asAdmin = false, ProcessW
{
if (e.NativeErrorCode != 1223)
throw;
-
+
// Nothing to handle on UAC cancellation
}
finally
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index 04ebd4160d..c0c8347d41 100644
--- a/Source/NETworkManager/ViewModels/FirewallViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
@@ -240,7 +240,7 @@ public bool ProfileFilterIsOpen
}
}
- ///
+ ///
/// Gets the collection view for profile filter tags.
///
public ICollectionView ProfileFilterTagsView { get; }
@@ -397,10 +397,10 @@ public FirewallViewModel()
_searchDispatcherTimer.Tick += SearchDispatcherTimer_Tick;
LoadSettings();
-
+
_isLoading = false;
}
-
+
///
/// Loads the settings.
///
@@ -754,7 +754,7 @@ private void DeleteProfileAction()
.ShowDeleteProfileDialog(Application.Current.MainWindow, this, new List { SelectedProfile })
.ConfigureAwait(false);
}
-
+
///
/// Gets the command to edit a profile group.
///
@@ -846,9 +846,9 @@ private void CollapseAllProfileGroupsAction()
{
SetIsExpandedForAllProfileGroups(false);
}
-
+
#region Additional commands
-
+
///
/// Gets the command to open the Windows Firewall management console (WF.msc).
///
@@ -872,7 +872,7 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
}
#endregion
-
+
#endregion
#region Methods
@@ -930,18 +930,18 @@ private async Task Refresh(bool init = false)
/// The dialog ViewModel containing the user's input.
private static FirewallRule BuildRule(FirewallRuleViewModel vm) => new()
{
- Name = vm.Name,
- IsEnabled = vm.IsEnabled,
- Description = vm.Description ?? string.Empty,
- Direction = vm.Direction,
- Action = vm.Action,
- Protocol = vm.Protocol,
- LocalPorts = ParsePortsString(vm.LocalPorts),
- RemotePorts = ParsePortsString(vm.RemotePorts),
- LocalAddresses = ParseAddressesString(vm.LocalAddresses),
+ Name = vm.Name,
+ IsEnabled = vm.IsEnabled,
+ Description = vm.Description ?? string.Empty,
+ Direction = vm.Direction,
+ Action = vm.Action,
+ Protocol = vm.Protocol,
+ LocalPorts = ParsePortsString(vm.LocalPorts),
+ RemotePorts = ParsePortsString(vm.RemotePorts),
+ LocalAddresses = ParseAddressesString(vm.LocalAddresses),
RemoteAddresses = ParseAddressesString(vm.RemoteAddresses),
- Program = string.IsNullOrWhiteSpace(vm.Program) ? null : new FirewallRuleProgram(vm.Program),
- InterfaceType = vm.InterfaceType,
+ Program = string.IsNullOrWhiteSpace(vm.Program) ? null : new FirewallRuleProgram(vm.Program),
+ InterfaceType = vm.InterfaceType,
NetworkProfiles = [vm.NetworkProfileDomain, vm.NetworkProfilePrivate, vm.NetworkProfilePublic]
};
@@ -998,7 +998,7 @@ private void SetIsExpandedForAllProfileGroups(bool isExpanded)
foreach (var group in Profiles.Groups.Cast())
GroupExpanderStateStore[group.Name.ToString()] = isExpanded;
}
-
+
///
/// Resizes the profile view.
///
@@ -1031,7 +1031,7 @@ private void ResizeProfile(bool dueToChangedSize)
_canProfileWidthChange = true;
}
-
+
///
/// Called when the view becomes visible.
///
@@ -1049,7 +1049,7 @@ public void OnViewHide()
{
_isViewActive = false;
}
-
+
///
/// Creates the profile filter tags.
///
@@ -1129,9 +1129,9 @@ private void RefreshProfiles()
IsProfileFilterSet = !string.IsNullOrEmpty(filter.Search) || filter.Tags.Any();
}
#endregion
-
+
#region Events
-
+
///
/// Handles the OnProfilesUpdated event of the ProfileManager.
///
@@ -1141,7 +1141,7 @@ private void ProfileManager_OnProfilesUpdated(object sender, EventArgs e)
RefreshProfiles();
}
-
+
///
/// Handles the Tick event of the search dispatcher timer.
///
@@ -1153,6 +1153,6 @@ private void SearchDispatcherTimer_Tick(object sender, EventArgs e)
IsSearching = false;
}
-
+
#endregion
}
diff --git a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
index 2aba68f586..cd16c741fd 100644
--- a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
+++ b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
@@ -900,7 +900,7 @@ private void ApplyConfigurationAction()
/// Gets the command to apply the profile configuration.
///
public ICommand ApplyProfileCommand => new RelayCommand(_ => ApplyProfileAction());
-
+
private void ApplyProfileAction()
{
ApplyConfigurationFromProfile().ConfigureAwait(false);
diff --git a/Source/NETworkManager/ViewModels/ProfileViewModel.cs b/Source/NETworkManager/ViewModels/ProfileViewModel.cs
index 5fe23f5577..dacde82a25 100644
--- a/Source/NETworkManager/ViewModels/ProfileViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ProfileViewModel.cs
@@ -310,7 +310,7 @@ public ProfileViewModel(Action saveCommand, Action
/// Offload event for toggling view to the view model.
///
From 0638b3680efbe8dcb8f6021647b52ed490eb03c7 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Fri, 24 Apr 2026 00:29:40 +0200
Subject: [PATCH 23/23] Update firewall.md
---
Website/docs/application/firewall.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Website/docs/application/firewall.md b/Website/docs/application/firewall.md
index 1692790754..be85cb5a21 100644
--- a/Website/docs/application/firewall.md
+++ b/Website/docs/application/firewall.md
@@ -12,14 +12,14 @@ The **Firewall** allows you to view, add, edit, enable, disable, or delete Windo
Windows Firewall (Windows Defender Firewall) is a built-in host-based firewall included with all versions of Windows. It filters inbound and outbound network traffic based on rules that define the protocol, port, address, program, and action (allow or block).
-Rules created by NETworkManager use the prefix `NETworkManager_` in their display name to distinguish them from system-managed or third-party rules. Only rules with this prefix are shown in the Firewall view.
-
:::
:::note
Adding, editing, enabling, disabling, or deleting firewall rules requires administrator privileges. If the application is not running as administrator, the view is in read-only mode. Use the **Restart as administrator** button to relaunch the application with elevated rights.
+Rules created by NETworkManager use the prefix `NETworkManager_` in their display name to distinguish them from system-managed or third-party rules. Only rules with this prefix are shown in the Firewall view.
+
:::