Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Source/NETworkManager.Localization/Resources/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions Source/NETworkManager.Localization/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -4729,4 +4729,15 @@ Cloudflare may log your IP address and network information. See Cloudflare's pri
<data name="CopyRule" xml:space="preserve">
<value>Copy rule</value>
</data>
<data name="DeleteFirewallRulesMessage" xml:space="preserve">
<value>The selected firewall rules are permanently deleted:

{0}</value>
</data>
<data name="DeleteRules" xml:space="preserve">
<value>Delete rules</value>
</data>
<data name="DeleteRulesX" xml:space="preserve">
<value>Delete rules ({0})</value>
</data>
</root>
91 changes: 59 additions & 32 deletions Source/NETworkManager.Models/Firewall/Firewall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,54 +168,77 @@ public static async Task<List<FirewallRule>> GetRulesAsync()
}

/// <summary>
/// Enables or disables the given <paramref name="rule"/> by running
/// <c>Enable-NetFirewallRule</c> or <c>Disable-NetFirewallRule</c> against
/// the rule's internal <see cref="FirewallRule.Id"/>.
/// Enables or disables the given <paramref name="rules"/> by running
/// <c>Enable-NetFirewallRule</c> or <c>Disable-NetFirewallRule</c> against the rules'
/// internal <see cref="FirewallRule.Id"/>. The cmdlet is run for all rules (also for rules that already
/// seem to be in the target state, since the state may have changed outside the application).
/// Nothing is changed if one of the rules no longer exists.
/// </summary>
/// <param name="rule">
/// The firewall rule to modify.
/// <param name="rules">
/// The firewall rules to modify.
/// </param>
/// <param name="enabled">
/// <see langword="true"/> to enable the rule; <see langword="false"/> to disable it.
/// <see langword="true"/> to enable the rules; <see langword="false"/> to disable them.
/// </param>
/// <exception cref="Exception">
/// Thrown when the PowerShell pipeline reports one or more errors.
/// Thrown when a rule no longer exists or the PowerShell pipeline reports one or more errors.
/// </exception>
public static async Task SetRuleEnabledAsync(FirewallRule rule, bool enabled)
public static Task SetRulesEnabledAsync(IEnumerable<FirewallRule> rules, bool enabled)
{
await RunspaceLock.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
{
RunspaceLock.Release();
}
return InvokeRuleCommandAsync(enabled ? "Enable-NetFirewallRule" : "Disable-NetFirewallRule",
rules.Select(rule => rule.Id).ToList());
}

/// <summary>
/// Permanently removes the given <paramref name="rule"/> by running
/// <c>Remove-NetFirewallRule</c> against the rule's internal <see cref="FirewallRule.Id"/>.
/// Permanently removes the given <paramref name="rule"/>.
/// </summary>
/// <param name="rule">
/// The firewall rule to delete.
/// </param>
/// <exception cref="Exception">
/// Thrown when the PowerShell pipeline reports one or more errors.
/// Thrown when the rule no longer exists or the PowerShell pipeline reports one or more errors.
/// </exception>
public static Task DeleteRuleAsync(FirewallRule rule)
{
return DeleteRulesAsync([rule]);
}

/// <summary>
/// Permanently removes the given <paramref name="rules"/> by running
/// <c>Remove-NetFirewallRule</c> against the rules' internal <see cref="FirewallRule.Id"/>.
/// Nothing is deleted if one of the rules no longer exists.
/// </summary>
/// <param name="rules">
/// The firewall rules to delete.
/// </param>
/// <exception cref="Exception">
/// Thrown when a rule no longer exists or the PowerShell pipeline reports one or more errors.
/// </exception>
public static Task DeleteRulesAsync(IEnumerable<FirewallRule> rules)
{
return InvokeRuleCommandAsync("Remove-NetFirewallRule", rules.Select(rule => rule.Id).ToList());
}

/// <summary>
/// Runs the given firewall cmdlet (e.g. <c>Enable-NetFirewallRule</c>) for all rule IDs in a
/// single PowerShell call. It is checked first that all rules still exist, otherwise nothing is changed.
/// This is not a transaction: if the cmdlet fails for a single rule (e.g. access denied), the other
/// rules may already be changed.
/// </summary>
/// <param name="command">
/// The cmdlet to run. Must accept the rule IDs via <c>-Name</c>.
/// </param>
/// <param name="ids">
/// The internal IDs of the rules. Nothing is done if the list is empty.
/// </param>
/// <exception cref="Exception">
/// Thrown when a rule no longer exists or the PowerShell pipeline reports one or more errors.
/// </exception>
public static async Task DeleteRuleAsync(FirewallRule rule)
private static async Task InvokeRuleCommandAsync(string command, IReadOnlyCollection<string> ids)
{
if (ids.Count == 0)
return;

await RunspaceLock.WaitAsync();
try
{
Expand All @@ -224,7 +247,11 @@ await Task.Run(() =>
using var ps = SMA.PowerShell.Create();
ps.Runspace = SharedRunspace;

ps.AddScript($@"Remove-NetFirewallRule -Name '{rule.Id}'");
// Check that all rules still exist before changing anything
ps.AddScript($@"
$ids = {ToPsArray(ids)}
Get-NetFirewallRule -Name $ids -ErrorAction Stop | Out-Null
{command} -Name $ids");
ps.Invoke();

if (ps.Streams.Error.Count > 0)
Expand Down
22 changes: 22 additions & 0 deletions Source/NETworkManager/DialogHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using NETworkManager.Utilities;
using NETworkManager.ViewModels;
using NETworkManager.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

Expand Down Expand Up @@ -94,5 +97,24 @@ public static async Task<bool> ShowConfirmationMessageAsync(Window parentWindow,

return result;
}

/// <summary>
/// Formats the given items as a list (one item per line) to show them in a dialog message,
/// e.g. the entries that will be deleted. Only the first <paramref name="maxItems"/> items are
/// listed, the remaining items are summarized as "(+X more)".
/// </summary>
/// <param name="items">The items to list.</param>
/// <param name="maxItems">The maximum number of items to list.</param>
/// <returns>The formatted list.</returns>
public static string FormatItemList(IEnumerable<string> items, int maxItems = 10)
{
var itemList = items.ToList();
var lines = itemList.Take(maxItems).ToList();

if (itemList.Count > maxItems)
lines.Add(string.Format(Strings.PlusXMore, itemList.Count - maxItems));

return string.Join(Environment.NewLine, lines);
}
}
}
Loading
Loading