mirror of
https://github.com/freedbygrace/PSOPNSenseAPI.git
synced 2026-08-21 07:37:34 +00:00
Fixed threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread
This commit is contained in:
@@ -76,178 +76,213 @@ namespace PSOPNSenseAPI.Cmdlets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the cmdlet
|
/// Processes the cmdlet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void ProcessRecord()
|
protected override void ProcessRecordInternal()
|
||||||
{
|
{
|
||||||
try
|
var tailscaleService = new TailscaleService(ApiClient, Logger);
|
||||||
|
|
||||||
|
// Check if the plugin is installed
|
||||||
|
var isInstalled = ExecuteAsyncTask(() => tailscaleService.IsPluginInstalledAsync());
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null)
|
||||||
{
|
{
|
||||||
var tailscaleService = new TailscaleService(ApiClient, Logger);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the plugin is installed
|
if (!isInstalled)
|
||||||
var isInstalledTask = Task.Run(async () => await tailscaleService.IsPluginInstalledAsync());
|
{
|
||||||
var isInstalled = isInstalledTask.GetAwaiter().GetResult();
|
if (!InstallIfMissing.IsPresent)
|
||||||
|
|
||||||
if (!isInstalled)
|
|
||||||
{
|
{
|
||||||
if (!InstallIfMissing.IsPresent)
|
ProcessingException = new Exception("Tailscale plugin is not installed. Use -InstallIfMissing to install it.");
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new Exception("Tailscale plugin is not installed. Use -InstallIfMissing to install it."),
|
|
||||||
"TailscalePluginNotInstalled",
|
|
||||||
ErrorCategory.InvalidOperation,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Install Tailscale plugin"))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteVerbose("Tailscale plugin is not installed. Installing...");
|
|
||||||
var installTask = Task.Run(async () => await tailscaleService.InstallPluginAsync());
|
|
||||||
var installResult = installTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
if (!installResult)
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new Exception("Failed to install Tailscale plugin."),
|
|
||||||
"TailscalePluginInstallFailed",
|
|
||||||
ErrorCategory.InvalidOperation,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteVerbose("Tailscale plugin installed successfully.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current status
|
|
||||||
var statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
|
|
||||||
var status = statusTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
// Enable Tailscale if needed
|
|
||||||
if (!status.Enabled && EnableIfDisabled.IsPresent)
|
|
||||||
{
|
|
||||||
WriteVerbose("Tailscale is disabled. Enabling...");
|
|
||||||
|
|
||||||
// Get current settings
|
|
||||||
var settingsTask = Task.Run(async () => await tailscaleService.GetSettingsAsync());
|
|
||||||
var currentSettings = settingsTask.GetAwaiter().GetResult().General;
|
|
||||||
|
|
||||||
// Process subnet routes if provided
|
|
||||||
string routesToAdvertise = currentSettings.RoutesToAdvertise;
|
|
||||||
bool advertiseRoutes = AdvertiseRoutes.IsPresent || currentSettings.AdvertiseRoutes == "1";
|
|
||||||
bool advertiseExitNode = AdvertiseExitNode.IsPresent || currentSettings.AdvertiseExitNode == "1";
|
|
||||||
|
|
||||||
if (SubnetRoutes != null && SubnetRoutes.Length > 0)
|
|
||||||
{
|
|
||||||
routesToAdvertise = string.Join(",", SubnetRoutes);
|
|
||||||
WriteVerbose($"Advertising subnet routes: {routesToAdvertise}");
|
|
||||||
|
|
||||||
// If routes are specified but AdvertiseRoutes is not set, enable it automatically
|
|
||||||
if (!advertiseRoutes)
|
|
||||||
{
|
|
||||||
WriteVerbose("Automatically enabling route advertisement because subnet routes were specified.");
|
|
||||||
advertiseRoutes = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new settings with enabled flag
|
|
||||||
var settings = new TailscaleSettings
|
|
||||||
{
|
|
||||||
Enabled = "1",
|
|
||||||
AcceptDns = currentSettings.AcceptDns,
|
|
||||||
AcceptRoutes = currentSettings.AcceptRoutes,
|
|
||||||
AdvertiseExitNode = advertiseExitNode ? "1" : "0",
|
|
||||||
AdvertiseRoutes = advertiseRoutes ? "1" : "0",
|
|
||||||
RoutesToAdvertise = routesToAdvertise,
|
|
||||||
Hostname = currentSettings.Hostname,
|
|
||||||
LoginServer = currentSettings.LoginServer,
|
|
||||||
Ssh = currentSettings.Ssh,
|
|
||||||
Ephemeral = currentSettings.Ephemeral,
|
|
||||||
ResetOnStart = currentSettings.ResetOnStart
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update settings
|
|
||||||
var updateTask = Task.Run(async () => await tailscaleService.UpdateSettingsAsync(settings));
|
|
||||||
var updateResult = updateTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
WriteVerbose($"Tailscale settings updated: {updateResult.Result}");
|
|
||||||
|
|
||||||
// Get updated status
|
|
||||||
statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
|
|
||||||
status = statusTask.GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the service if needed
|
|
||||||
if (!status.Running && StartIfStopped.IsPresent)
|
|
||||||
{
|
|
||||||
WriteVerbose("Tailscale service is not running. Starting...");
|
|
||||||
var startTask = Task.Run(async () => await tailscaleService.StartServiceAsync());
|
|
||||||
var startResult = startTask.GetAwaiter().GetResult();
|
|
||||||
WriteVerbose($"Tailscale service started: {startResult.Status}");
|
|
||||||
|
|
||||||
// Get updated status
|
|
||||||
statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
|
|
||||||
status = statusTask.GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Tailscale is enabled and running
|
|
||||||
if (!status.Enabled)
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new Exception("Tailscale is not enabled. Use -EnableIfDisabled to enable it."),
|
|
||||||
"TailscaleNotEnabled",
|
|
||||||
ErrorCategory.InvalidOperation,
|
|
||||||
null));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!status.Running)
|
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Install Tailscale plugin"))
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new Exception("Tailscale service is not running. Use -StartIfStopped to start it."),
|
|
||||||
"TailscaleNotRunning",
|
|
||||||
ErrorCategory.InvalidOperation,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Connect to Tailscale network"))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to Tailscale
|
WriteVerbose("Tailscale plugin is not installed. Installing...");
|
||||||
WriteVerbose("Connecting to Tailscale network...");
|
var installResult = ExecuteAsyncTask(() => tailscaleService.InstallPluginAsync());
|
||||||
var connectTask = Task.Run(async () => await tailscaleService.ConnectAsync(AuthKey));
|
|
||||||
var connectResult = connectTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
WriteVerbose($"Tailscale connection status: {connectResult.Status}");
|
// Only continue if no exception occurred
|
||||||
WriteVerbose($"Tailscale connection message: {connectResult.Message}");
|
if (ProcessingException != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!installResult)
|
||||||
|
{
|
||||||
|
ProcessingException = new Exception("Failed to install Tailscale plugin.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteVerbose("Tailscale plugin installed successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current status
|
||||||
|
var status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || status == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable Tailscale if needed
|
||||||
|
if (!status.Enabled && EnableIfDisabled.IsPresent)
|
||||||
|
{
|
||||||
|
WriteVerbose("Tailscale is disabled. Enabling...");
|
||||||
|
|
||||||
|
// Get current settings
|
||||||
|
var settingsResult = ExecuteAsyncTask(() => tailscaleService.GetSettingsAsync());
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || settingsResult == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentSettings = settingsResult.General;
|
||||||
|
|
||||||
|
// Process subnet routes if provided
|
||||||
|
string routesToAdvertise = currentSettings.RoutesToAdvertise;
|
||||||
|
bool advertiseRoutes = AdvertiseRoutes.IsPresent || currentSettings.AdvertiseRoutes == "1";
|
||||||
|
bool advertiseExitNode = AdvertiseExitNode.IsPresent || currentSettings.AdvertiseExitNode == "1";
|
||||||
|
|
||||||
|
if (SubnetRoutes != null && SubnetRoutes.Length > 0)
|
||||||
|
{
|
||||||
|
routesToAdvertise = string.Join(",", SubnetRoutes);
|
||||||
|
WriteVerbose($"Advertising subnet routes: {routesToAdvertise}");
|
||||||
|
|
||||||
|
// If routes are specified but AdvertiseRoutes is not set, enable it automatically
|
||||||
|
if (!advertiseRoutes)
|
||||||
|
{
|
||||||
|
WriteVerbose("Automatically enabling route advertisement because subnet routes were specified.");
|
||||||
|
advertiseRoutes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new settings with enabled flag
|
||||||
|
var settings = new TailscaleSettings
|
||||||
|
{
|
||||||
|
Enabled = "1",
|
||||||
|
AcceptDns = currentSettings.AcceptDns,
|
||||||
|
AcceptRoutes = currentSettings.AcceptRoutes,
|
||||||
|
AdvertiseExitNode = advertiseExitNode ? "1" : "0",
|
||||||
|
AdvertiseRoutes = advertiseRoutes ? "1" : "0",
|
||||||
|
RoutesToAdvertise = routesToAdvertise,
|
||||||
|
Hostname = currentSettings.Hostname,
|
||||||
|
LoginServer = currentSettings.LoginServer,
|
||||||
|
Ssh = currentSettings.Ssh,
|
||||||
|
Ephemeral = currentSettings.Ephemeral,
|
||||||
|
ResetOnStart = currentSettings.ResetOnStart
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update settings
|
||||||
|
var updateResult = ExecuteAsyncTask(() => tailscaleService.UpdateSettingsAsync(settings));
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || updateResult == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteVerbose($"Tailscale settings updated: {updateResult.Result}");
|
||||||
|
|
||||||
// Get updated status
|
// Get updated status
|
||||||
statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
|
status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
|
||||||
status = statusTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
// Get interfaces
|
// Only continue if no exception occurred
|
||||||
var interfacesTask = Task.Run(async () => await tailscaleService.GetInterfacesAsync());
|
if (ProcessingException != null || status == null)
|
||||||
var interfaces = interfacesTask.GetAwaiter().GetResult();
|
{
|
||||||
|
return;
|
||||||
// Create result object
|
}
|
||||||
var result = new PSObject();
|
|
||||||
result.Properties.Add(new PSNoteProperty("Status", status.Status));
|
|
||||||
result.Properties.Add(new PSNoteProperty("Running", status.Running));
|
|
||||||
result.Properties.Add(new PSNoteProperty("Enabled", status.Enabled));
|
|
||||||
result.Properties.Add(new PSNoteProperty("ConnectionStatus", connectResult.Status));
|
|
||||||
result.Properties.Add(new PSNoteProperty("ConnectionMessage", connectResult.Message));
|
|
||||||
result.Properties.Add(new PSNoteProperty("Interfaces", interfaces.Interfaces));
|
|
||||||
|
|
||||||
WriteObject(result);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
// Start the service if needed
|
||||||
|
if (!status.Running && StartIfStopped.IsPresent)
|
||||||
{
|
{
|
||||||
HandleException(ex);
|
WriteVerbose("Tailscale service is not running. Starting...");
|
||||||
|
var startResult = ExecuteAsyncTask(() => tailscaleService.StartServiceAsync());
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || startResult == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteVerbose($"Tailscale service started: {startResult.Status}");
|
||||||
|
|
||||||
|
// Get updated status
|
||||||
|
status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || status == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if Tailscale is enabled and running
|
||||||
|
if (!status.Enabled)
|
||||||
|
{
|
||||||
|
ProcessingException = new Exception("Tailscale is not enabled. Use -EnableIfDisabled to enable it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!status.Running)
|
||||||
|
{
|
||||||
|
ProcessingException = new Exception("Tailscale service is not running. Use -StartIfStopped to start it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Connect to Tailscale network"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to Tailscale
|
||||||
|
WriteVerbose("Connecting to Tailscale network...");
|
||||||
|
var connectResult = ExecuteAsyncTask(() => tailscaleService.ConnectAsync(AuthKey));
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || connectResult == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteVerbose($"Tailscale connection status: {connectResult.Status}");
|
||||||
|
WriteVerbose($"Tailscale connection message: {connectResult.Message}");
|
||||||
|
|
||||||
|
// Get updated status
|
||||||
|
status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || status == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get interfaces
|
||||||
|
var interfaces = ExecuteAsyncTask(() => tailscaleService.GetInterfacesAsync());
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || interfaces == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create result object
|
||||||
|
var result = new PSObject();
|
||||||
|
result.Properties.Add(new PSNoteProperty("Status", status.Status));
|
||||||
|
result.Properties.Add(new PSNoteProperty("Running", status.Running));
|
||||||
|
result.Properties.Add(new PSNoteProperty("Enabled", status.Enabled));
|
||||||
|
result.Properties.Add(new PSNoteProperty("ConnectionStatus", connectResult.Status));
|
||||||
|
result.Properties.Add(new PSNoteProperty("ConnectionMessage", connectResult.Message));
|
||||||
|
result.Properties.Add(new PSNoteProperty("Interfaces", interfaces.Interfaces));
|
||||||
|
|
||||||
|
WriteObject(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Management.Automation;
|
using System.Management.Automation;
|
||||||
|
using PSOPNSenseAPI.Logging;
|
||||||
using PSOPNSenseAPI.Models;
|
using PSOPNSenseAPI.Models;
|
||||||
|
|
||||||
namespace PSOPNSenseAPI.Cmdlets
|
namespace PSOPNSenseAPI.Cmdlets
|
||||||
@@ -15,35 +16,32 @@ namespace PSOPNSenseAPI.Cmdlets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Cmdlet(VerbsCommon.Get, "OPNSenseConnection")]
|
[Cmdlet(VerbsCommon.Get, "OPNSenseConnection")]
|
||||||
[OutputType(typeof(PSObject))]
|
[OutputType(typeof(PSObject))]
|
||||||
public class GetOPNSenseConnectionCmdlet : PSCmdlet
|
public class GetOPNSenseConnectionCmdlet : OPNSenseBaseCmdlet
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the cmdlet
|
/// Processes the cmdlet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void ProcessRecord()
|
protected override void BeginProcessing()
|
||||||
{
|
{
|
||||||
try
|
// Override the base implementation to avoid checking for connection
|
||||||
{
|
// since this cmdlet is used to check the connection status
|
||||||
if (!OPNSenseSession.IsConnected)
|
base.BeginProcessing();
|
||||||
{
|
Logger = new PowerShellLogger(this);
|
||||||
WriteWarning("Not connected to any OPNSense firewall.");
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var connectionInfo = new PSObject();
|
protected override void ProcessRecordInternal()
|
||||||
connectionInfo.Properties.Add(new PSNoteProperty("Server", OPNSenseSession.BaseUrl));
|
{
|
||||||
connectionInfo.Properties.Add(new PSNoteProperty("Connected", OPNSenseSession.IsConnected));
|
if (!OPNSenseSession.IsConnected)
|
||||||
|
|
||||||
WriteObject(connectionInfo);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
WriteError(new ErrorRecord(
|
WriteWarning("Not connected to any OPNSense firewall.");
|
||||||
ex,
|
return;
|
||||||
"GetConnectionFailed",
|
|
||||||
ErrorCategory.ConnectionError,
|
|
||||||
null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var connectionInfo = new PSObject();
|
||||||
|
connectionInfo.Properties.Add(new PSNoteProperty("Server", OPNSenseSession.BaseUrl));
|
||||||
|
connectionInfo.Properties.Add(new PSNoteProperty("Connected", OPNSenseSession.IsConnected));
|
||||||
|
|
||||||
|
WriteObject(connectionInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Management.Automation;
|
using System.Management.Automation;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using PSOPNSenseAPI.Logging;
|
||||||
using PSOPNSenseAPI.Utilities;
|
using PSOPNSenseAPI.Utilities;
|
||||||
|
|
||||||
namespace PSOPNSenseAPI.Cmdlets
|
namespace PSOPNSenseAPI.Cmdlets
|
||||||
@@ -27,7 +28,7 @@ namespace PSOPNSenseAPI.Cmdlets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Cmdlet(VerbsLifecycle.Invoke, "OPNSenseNetworkCalculation")]
|
[Cmdlet(VerbsLifecycle.Invoke, "OPNSenseNetworkCalculation")]
|
||||||
[OutputType(typeof(PSObject), typeof(bool), typeof(IPNetwork2[]))]
|
[OutputType(typeof(PSObject), typeof(bool), typeof(IPNetwork2[]))]
|
||||||
public class InvokeOPNSenseNetworkCalculationCmdlet : PSCmdlet
|
public class InvokeOPNSenseNetworkCalculationCmdlet : OPNSenseBaseCmdlet
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para type="description">The network in CIDR notation to perform calculations on.</para>
|
/// <para type="description">The network in CIDR notation to perform calculations on.</para>
|
||||||
@@ -72,111 +73,93 @@ namespace PSOPNSenseAPI.Cmdlets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the cmdlet
|
/// Processes the cmdlet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void ProcessRecord()
|
protected override void BeginProcessing()
|
||||||
{
|
{
|
||||||
|
// Override the base implementation to avoid checking for connection
|
||||||
|
// since this cmdlet doesn't require a connection
|
||||||
|
base.BeginProcessing();
|
||||||
|
Logger = new PowerShellLogger(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ProcessRecordInternal()
|
||||||
|
{
|
||||||
|
// Parse the network
|
||||||
|
IPNetwork2 network;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Parse the network
|
network = NetworkUtility.ParseCIDR(Network);
|
||||||
IPNetwork2 network = NetworkUtility.ParseCIDR(Network);
|
|
||||||
|
|
||||||
switch (Operation)
|
|
||||||
{
|
|
||||||
case "Info":
|
|
||||||
WriteNetworkInfo(network);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Subnet":
|
|
||||||
if (!PrefixLength.HasValue)
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new ArgumentException("PrefixLength is required for Subnet operations."),
|
|
||||||
"MissingPrefixLength",
|
|
||||||
ErrorCategory.InvalidArgument,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteSubnets(network, PrefixLength.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "SubnetByCount":
|
|
||||||
if (!SubnetCount.HasValue)
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new ArgumentException("SubnetCount is required for SubnetByCount operations."),
|
|
||||||
"MissingSubnetCount",
|
|
||||||
ErrorCategory.InvalidArgument,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteSubnetsByCount(network, SubnetCount.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Contains":
|
|
||||||
if (string.IsNullOrEmpty(IPAddress))
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new ArgumentException("IPAddress is required for Contains operations."),
|
|
||||||
"MissingIPAddress",
|
|
||||||
ErrorCategory.InvalidArgument,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteContains(network, IPAddress);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Overlaps":
|
|
||||||
if (AdditionalNetworks == null || AdditionalNetworks.Length == 0)
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new ArgumentException("AdditionalNetworks is required for Overlaps operations."),
|
|
||||||
"MissingAdditionalNetworks",
|
|
||||||
ErrorCategory.InvalidArgument,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteOverlaps(network, AdditionalNetworks);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Supernet":
|
|
||||||
if (AdditionalNetworks == null || AdditionalNetworks.Length == 0)
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new ArgumentException("AdditionalNetworks is required for Supernet operations."),
|
|
||||||
"MissingAdditionalNetworks",
|
|
||||||
ErrorCategory.InvalidArgument,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteSupernet(network, AdditionalNetworks);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "SupernetSummarize":
|
|
||||||
if (AdditionalNetworks == null || AdditionalNetworks.Length == 0)
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new ArgumentException("AdditionalNetworks is required for SupernetSummarize operations."),
|
|
||||||
"MissingAdditionalNetworks",
|
|
||||||
ErrorCategory.InvalidArgument,
|
|
||||||
null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteSupernetSummarize(network, AdditionalNetworks);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
WriteError(new ErrorRecord(
|
ProcessingException = new ArgumentException($"Invalid network format: {Network}", ex);
|
||||||
ex,
|
return;
|
||||||
"NetworkCalculationError",
|
}
|
||||||
ErrorCategory.InvalidOperation,
|
|
||||||
null));
|
switch (Operation)
|
||||||
|
{
|
||||||
|
case "Info":
|
||||||
|
WriteNetworkInfo(network);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Subnet":
|
||||||
|
if (!PrefixLength.HasValue)
|
||||||
|
{
|
||||||
|
ProcessingException = new ArgumentException("PrefixLength is required for Subnet operations.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSubnets(network, PrefixLength.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "SubnetByCount":
|
||||||
|
if (!SubnetCount.HasValue)
|
||||||
|
{
|
||||||
|
ProcessingException = new ArgumentException("SubnetCount is required for SubnetByCount operations.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSubnetsByCount(network, SubnetCount.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Contains":
|
||||||
|
if (string.IsNullOrEmpty(IPAddress))
|
||||||
|
{
|
||||||
|
ProcessingException = new ArgumentException("IPAddress is required for Contains operations.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteContains(network, IPAddress);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Overlaps":
|
||||||
|
if (AdditionalNetworks == null || AdditionalNetworks.Length == 0)
|
||||||
|
{
|
||||||
|
ProcessingException = new ArgumentException("AdditionalNetworks is required for Overlaps operations.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteOverlaps(network, AdditionalNetworks);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Supernet":
|
||||||
|
if (AdditionalNetworks == null || AdditionalNetworks.Length == 0)
|
||||||
|
{
|
||||||
|
ProcessingException = new ArgumentException("AdditionalNetworks is required for Supernet operations.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSupernet(network, AdditionalNetworks);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "SupernetSummarize":
|
||||||
|
if (AdditionalNetworks == null || AdditionalNetworks.Length == 0)
|
||||||
|
{
|
||||||
|
ProcessingException = new ArgumentException("AdditionalNetworks is required for SupernetSummarize operations.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSupernetSummarize(network, AdditionalNetworks);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,44 +82,49 @@ namespace PSOPNSenseAPI.Cmdlets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the cmdlet
|
/// Processes the cmdlet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void ProcessRecord()
|
protected override void ProcessRecordInternal()
|
||||||
{
|
{
|
||||||
try
|
var cronService = new CronService(ApiClient, Logger);
|
||||||
|
|
||||||
|
var job = new CronJobConfig
|
||||||
{
|
{
|
||||||
var cronService = new CronService(ApiClient, Logger);
|
Description = Description,
|
||||||
|
Command = Command,
|
||||||
|
Minutes = Minutes,
|
||||||
|
Hours = Hours,
|
||||||
|
Days = Days,
|
||||||
|
Months = Months,
|
||||||
|
Weekdays = Weekdays,
|
||||||
|
Enabled = Enabled.IsPresent ? "1" : "0"
|
||||||
|
};
|
||||||
|
|
||||||
var job = new CronJobConfig
|
// Use our safe execution method
|
||||||
|
var createResult = ExecuteAsyncTask(() => cronService.CreateJobAsync(job));
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || createResult == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteVerbose($"Created cron job with UUID {createResult.Uuid}");
|
||||||
|
|
||||||
|
// Apply the changes if requested
|
||||||
|
if (Apply.IsPresent)
|
||||||
|
{
|
||||||
|
// Use our safe execution method
|
||||||
|
var applyResult = ExecuteAsyncTask(() => cronService.ApplyChangesAsync());
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || applyResult == null)
|
||||||
{
|
{
|
||||||
Description = Description,
|
return;
|
||||||
Command = Command,
|
|
||||||
Minutes = Minutes,
|
|
||||||
Hours = Hours,
|
|
||||||
Days = Days,
|
|
||||||
Months = Months,
|
|
||||||
Weekdays = Weekdays,
|
|
||||||
Enabled = Enabled.IsPresent ? "1" : "0"
|
|
||||||
};
|
|
||||||
|
|
||||||
var createTask = Task.Run(async () => await cronService.CreateJobAsync(job));
|
|
||||||
var createResult = createTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
WriteVerbose($"Created cron job with UUID {createResult.Uuid}");
|
|
||||||
|
|
||||||
// Apply the changes if requested
|
|
||||||
if (Apply.IsPresent)
|
|
||||||
{
|
|
||||||
var applyTask = Task.Run(async () => await cronService.ApplyChangesAsync());
|
|
||||||
var applyResult = applyTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
WriteVerbose($"Cron changes applied: {applyResult.Status}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteObject(createResult.Uuid);
|
WriteVerbose($"Cron changes applied: {applyResult.Status}");
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
HandleException(ex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WriteObject(createResult.Uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,14 +73,14 @@ namespace PSOPNSenseAPI.Cmdlets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the logger
|
/// Gets the logger
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ILogger Logger { get; private set; }
|
protected ILogger Logger { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exception to be processed in ProcessRecord
|
/// Exception to be processed in ProcessRecord
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Exception ProcessingException { get; private set; }
|
protected Exception ProcessingException { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles exceptions by storing them for later processing
|
/// Handles exceptions by storing them for later processing
|
||||||
|
|||||||
@@ -60,67 +60,69 @@ namespace PSOPNSenseAPI.Cmdlets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the cmdlet
|
/// Processes the cmdlet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void ProcessRecord()
|
protected override void ProcessRecordInternal()
|
||||||
{
|
{
|
||||||
try
|
if (!Force.IsPresent && !ShouldProcess(Name, "Uninstall plugin"))
|
||||||
{
|
{
|
||||||
if (!Force.IsPresent && !ShouldProcess(Name, "Uninstall plugin"))
|
return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pluginService = new PluginService(ApiClient, Logger);
|
|
||||||
|
|
||||||
var task = Task.Run(async () => await pluginService.UninstallPluginAsync(Name));
|
|
||||||
var result = task.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
WriteVerbose($"Plugin uninstallation initiated: {result.Status}");
|
|
||||||
WriteObject($"Plugin uninstallation initiated: {result.Status}");
|
|
||||||
|
|
||||||
if (Wait.IsPresent)
|
|
||||||
{
|
|
||||||
WriteVerbose($"Waiting for plugin uninstallation to complete (timeout: {Timeout} seconds, interval: {Interval} seconds)");
|
|
||||||
WriteObject("Waiting for plugin uninstallation to complete...");
|
|
||||||
|
|
||||||
DateTime startTime = DateTime.Now;
|
|
||||||
bool completed = false;
|
|
||||||
|
|
||||||
while (DateTime.Now - startTime < TimeSpan.FromSeconds(Timeout))
|
|
||||||
{
|
|
||||||
var statusTask = Task.Run(async () => await pluginService.GetPluginStatusAsync());
|
|
||||||
var statusResult = statusTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
if (statusResult.Status == "done")
|
|
||||||
{
|
|
||||||
WriteVerbose("Plugin uninstallation completed successfully");
|
|
||||||
WriteObject("Plugin uninstallation completed successfully");
|
|
||||||
completed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (statusResult.Status == "error")
|
|
||||||
{
|
|
||||||
WriteError(new ErrorRecord(
|
|
||||||
new Exception($"Plugin uninstallation failed: {statusResult.Log}"),
|
|
||||||
"PluginUninstallationFailed",
|
|
||||||
ErrorCategory.InvalidOperation,
|
|
||||||
Name));
|
|
||||||
completed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteVerbose($"Plugin uninstallation status: {statusResult.Status}");
|
|
||||||
Thread.Sleep(Interval * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!completed)
|
|
||||||
{
|
|
||||||
WriteWarning($"Timed out waiting for plugin uninstallation to complete after {Timeout} seconds");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
var pluginService = new PluginService(ApiClient, Logger);
|
||||||
|
|
||||||
|
// Use our safe execution method
|
||||||
|
var result = ExecuteAsyncTask(() => pluginService.UninstallPluginAsync(Name));
|
||||||
|
|
||||||
|
// Only continue if no exception occurred
|
||||||
|
if (ProcessingException != null || result == null)
|
||||||
{
|
{
|
||||||
HandleException(ex);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteVerbose($"Plugin uninstallation initiated: {result.Status}");
|
||||||
|
WriteObject($"Plugin uninstallation initiated: {result.Status}");
|
||||||
|
|
||||||
|
if (Wait.IsPresent)
|
||||||
|
{
|
||||||
|
WriteVerbose($"Waiting for plugin uninstallation to complete (timeout: {Timeout} seconds, interval: {Interval} seconds)");
|
||||||
|
WriteObject("Waiting for plugin uninstallation to complete...");
|
||||||
|
|
||||||
|
DateTime startTime = DateTime.Now;
|
||||||
|
bool completed = false;
|
||||||
|
|
||||||
|
while (DateTime.Now - startTime < TimeSpan.FromSeconds(Timeout))
|
||||||
|
{
|
||||||
|
// Use our safe execution method
|
||||||
|
var statusResult = ExecuteAsyncTask(() => pluginService.GetPluginStatusAsync());
|
||||||
|
|
||||||
|
// Break if an exception occurred
|
||||||
|
if (ProcessingException != null || statusResult == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusResult.Status == "done")
|
||||||
|
{
|
||||||
|
WriteVerbose("Plugin uninstallation completed successfully");
|
||||||
|
WriteObject("Plugin uninstallation completed successfully");
|
||||||
|
completed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (statusResult.Status == "error")
|
||||||
|
{
|
||||||
|
// Store the exception to be processed in ProcessRecord
|
||||||
|
ProcessingException = new Exception($"Plugin uninstallation failed: {statusResult.Log}");
|
||||||
|
completed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteVerbose($"Plugin uninstallation status: {statusResult.Status}");
|
||||||
|
Thread.Sleep(Interval * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!completed && ProcessingException == null)
|
||||||
|
{
|
||||||
|
WriteWarning($"Timed out waiting for plugin uninstallation to complete after {Timeout} seconds");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
RootModule = 'lib\PSOPNSenseAPI.dll'
|
RootModule = 'lib\PSOPNSenseAPI.dll'
|
||||||
|
|
||||||
# Version number of this module.
|
# Version number of this module.
|
||||||
ModuleVersion = '2025.04.15.1216'
|
ModuleVersion = '2025.04.15.1228'
|
||||||
|
|
||||||
# Supported PSEditions
|
# Supported PSEditions
|
||||||
CompatiblePSEditions = @('Desktop', 'Core')
|
CompatiblePSEditions = @('Desktop', 'Core')
|
||||||
|
|||||||
Reference in New Issue
Block a user