From 9c4d3f2eb3b16b2538e46eca84289ddfd7ebcf1c Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Tue, 15 Apr 2025 12:29:12 -0400 Subject: [PATCH] Fixed threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread --- .../Cmdlets/ConnectOPNSenseTailscaleCmdlet.cs | 343 ++++++++++-------- .../Cmdlets/GetOPNSenseConnectionCmdlet.cs | 40 +- .../InvokeOPNSenseNetworkCalculationCmdlet.cs | 183 +++++----- .../Cmdlets/NewOPNSenseCronJobCmdlet.cs | 67 ++-- .../Cmdlets/OPNSenseBaseCmdlet.cs | 4 +- .../Cmdlets/UninstallOPNSensePluginCmdlet.cs | 116 +++--- src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 | 2 +- 7 files changed, 389 insertions(+), 366 deletions(-) diff --git a/src/PSOPNSenseAPI/Cmdlets/ConnectOPNSenseTailscaleCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/ConnectOPNSenseTailscaleCmdlet.cs index 0e5fd4b..60b5d7c 100644 --- a/src/PSOPNSenseAPI/Cmdlets/ConnectOPNSenseTailscaleCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/ConnectOPNSenseTailscaleCmdlet.cs @@ -76,178 +76,213 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - 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 - var isInstalledTask = Task.Run(async () => await tailscaleService.IsPluginInstalledAsync()); - var isInstalled = isInstalledTask.GetAwaiter().GetResult(); - - if (!isInstalled) + if (!isInstalled) + { + if (!InstallIfMissing.IsPresent) { - if (!InstallIfMissing.IsPresent) - { - 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)); + ProcessingException = new Exception("Tailscale plugin is not installed. Use -InstallIfMissing to install it."); return; } - if (!status.Running) - { - 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")) + if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Install Tailscale plugin")) { return; } - // Connect to Tailscale - WriteVerbose("Connecting to Tailscale network..."); - var connectTask = Task.Run(async () => await tailscaleService.ConnectAsync(AuthKey)); - var connectResult = connectTask.GetAwaiter().GetResult(); + WriteVerbose("Tailscale plugin is not installed. Installing..."); + var installResult = ExecuteAsyncTask(() => tailscaleService.InstallPluginAsync()); - WriteVerbose($"Tailscale connection status: {connectResult.Status}"); - WriteVerbose($"Tailscale connection message: {connectResult.Message}"); + // Only continue if no exception occurred + 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 - statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync()); - status = statusTask.GetAwaiter().GetResult(); + status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync()); - // Get interfaces - var interfacesTask = Task.Run(async () => await tailscaleService.GetInterfacesAsync()); - var interfaces = interfacesTask.GetAwaiter().GetResult(); - - // 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); + // Only continue if no exception occurred + if (ProcessingException != null || status == null) + { + return; + } } - 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); } } } diff --git a/src/PSOPNSenseAPI/Cmdlets/GetOPNSenseConnectionCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/GetOPNSenseConnectionCmdlet.cs index e69259d..b4f5bb3 100644 --- a/src/PSOPNSenseAPI/Cmdlets/GetOPNSenseConnectionCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/GetOPNSenseConnectionCmdlet.cs @@ -1,5 +1,6 @@ using System; using System.Management.Automation; +using PSOPNSenseAPI.Logging; using PSOPNSenseAPI.Models; namespace PSOPNSenseAPI.Cmdlets @@ -15,35 +16,32 @@ namespace PSOPNSenseAPI.Cmdlets /// [Cmdlet(VerbsCommon.Get, "OPNSenseConnection")] [OutputType(typeof(PSObject))] - public class GetOPNSenseConnectionCmdlet : PSCmdlet + public class GetOPNSenseConnectionCmdlet : OPNSenseBaseCmdlet { /// /// Processes the cmdlet /// - protected override void ProcessRecord() + protected override void BeginProcessing() { - try - { - if (!OPNSenseSession.IsConnected) - { - WriteWarning("Not connected to any OPNSense firewall."); - return; - } + // Override the base implementation to avoid checking for connection + // since this cmdlet is used to check the connection status + base.BeginProcessing(); + Logger = new PowerShellLogger(this); + } - var connectionInfo = new PSObject(); - connectionInfo.Properties.Add(new PSNoteProperty("Server", OPNSenseSession.BaseUrl)); - connectionInfo.Properties.Add(new PSNoteProperty("Connected", OPNSenseSession.IsConnected)); - - WriteObject(connectionInfo); - } - catch (Exception ex) + protected override void ProcessRecordInternal() + { + if (!OPNSenseSession.IsConnected) { - WriteError(new ErrorRecord( - ex, - "GetConnectionFailed", - ErrorCategory.ConnectionError, - null)); + WriteWarning("Not connected to any OPNSense firewall."); + return; } + + var connectionInfo = new PSObject(); + connectionInfo.Properties.Add(new PSNoteProperty("Server", OPNSenseSession.BaseUrl)); + connectionInfo.Properties.Add(new PSNoteProperty("Connected", OPNSenseSession.IsConnected)); + + WriteObject(connectionInfo); } } } diff --git a/src/PSOPNSenseAPI/Cmdlets/InvokeOPNSenseNetworkCalculationCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/InvokeOPNSenseNetworkCalculationCmdlet.cs index 2f67113..8cb1d69 100644 --- a/src/PSOPNSenseAPI/Cmdlets/InvokeOPNSenseNetworkCalculationCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/InvokeOPNSenseNetworkCalculationCmdlet.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Management.Automation; using System.Net; +using PSOPNSenseAPI.Logging; using PSOPNSenseAPI.Utilities; namespace PSOPNSenseAPI.Cmdlets @@ -27,7 +28,7 @@ namespace PSOPNSenseAPI.Cmdlets /// [Cmdlet(VerbsLifecycle.Invoke, "OPNSenseNetworkCalculation")] [OutputType(typeof(PSObject), typeof(bool), typeof(IPNetwork2[]))] - public class InvokeOPNSenseNetworkCalculationCmdlet : PSCmdlet + public class InvokeOPNSenseNetworkCalculationCmdlet : OPNSenseBaseCmdlet { /// /// The network in CIDR notation to perform calculations on. @@ -72,111 +73,93 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - 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 { - // Parse the 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; - } + network = NetworkUtility.ParseCIDR(Network); } catch (Exception ex) { - WriteError(new ErrorRecord( - ex, - "NetworkCalculationError", - ErrorCategory.InvalidOperation, - null)); + ProcessingException = new ArgumentException($"Invalid network format: {Network}", ex); + return; + } + + 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; } } diff --git a/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseCronJobCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseCronJobCmdlet.cs index 59f0834..b36efc3 100644 --- a/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseCronJobCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseCronJobCmdlet.cs @@ -82,44 +82,49 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - 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, - 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}"); + return; } - WriteObject(createResult.Uuid); - } - catch (Exception ex) - { - HandleException(ex); + WriteVerbose($"Cron changes applied: {applyResult.Status}"); } + + WriteObject(createResult.Uuid); } } } diff --git a/src/PSOPNSenseAPI/Cmdlets/OPNSenseBaseCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/OPNSenseBaseCmdlet.cs index f154bfb..dfa1519 100644 --- a/src/PSOPNSenseAPI/Cmdlets/OPNSenseBaseCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/OPNSenseBaseCmdlet.cs @@ -73,14 +73,14 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Gets the logger /// - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; set; } /// /// Exception to be processed in ProcessRecord /// - protected Exception ProcessingException { get; private set; } + protected Exception ProcessingException { get; set; } /// /// Handles exceptions by storing them for later processing diff --git a/src/PSOPNSenseAPI/Cmdlets/UninstallOPNSensePluginCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/UninstallOPNSensePluginCmdlet.cs index 751de84..65548eb 100644 --- a/src/PSOPNSenseAPI/Cmdlets/UninstallOPNSensePluginCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/UninstallOPNSensePluginCmdlet.cs @@ -60,67 +60,69 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - protected override void ProcessRecord() + protected override void ProcessRecordInternal() { - try + if (!Force.IsPresent && !ShouldProcess(Name, "Uninstall plugin")) { - if (!Force.IsPresent && !ShouldProcess(Name, "Uninstall plugin")) - { - 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"); - } - } + return; } - 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"); + } } } } diff --git a/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 b/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 index e99512f..875175e 100644 --- a/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 +++ b/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 @@ -3,7 +3,7 @@ RootModule = 'lib\PSOPNSenseAPI.dll' # Version number of this module. - ModuleVersion = '2025.04.15.1216' + ModuleVersion = '2025.04.15.1228' # Supported PSEditions CompatiblePSEditions = @('Desktop', 'Core')