From 570860bdc206244cf6dd76fc89818087e29e2614 Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Tue, 15 Apr 2025 12:58:23 -0400 Subject: [PATCH] Fixed more threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread --- .../Cmdlets/NewOPNSenseAliasCmdlet.cs | 91 +++++---- .../Cmdlets/NewOPNSenseDHCPOptionCmdlet.cs | 59 +++--- .../NewOPNSenseDHCPStaticMappingCmdlet.cs | 61 +++--- .../Cmdlets/RestartOPNSenseFirewallCmdlet.cs | 191 +++++++++--------- .../Cmdlets/RestartOPNSenseInterfaceCmdlet.cs | 29 ++- src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 | 2 +- 6 files changed, 224 insertions(+), 209 deletions(-) diff --git a/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseAliasCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseAliasCmdlet.cs index d4eafde..be34be9 100644 --- a/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseAliasCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseAliasCmdlet.cs @@ -95,54 +95,59 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - protected override void ProcessRecord() + protected override void ProcessRecordInternal() { - try + var aliasService = new AliasService(ApiClient, Logger); + + var alias = new AliasConfig { - var aliasService = new AliasService(ApiClient, Logger); + Name = Name, + Type = Type.ToLower(), + Content = Content, + Description = Description, + Enabled = Disabled.IsPresent ? "0" : "1", + Counters = EnableCounters.IsPresent ? "1" : "0" + }; - var alias = new AliasConfig - { - Name = Name, - Type = Type.ToLower(), - Content = Content, - Description = Description, - Enabled = Disabled.IsPresent ? "0" : "1", - Counters = EnableCounters.IsPresent ? "1" : "0" - }; - - // Set protocol for port aliases - if (Type.Equals("port", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(Protocol)) - { - alias.Protocol = Protocol.ToUpper(); - } - - // Set update frequency for URL aliases - if ((Type.Equals("url", StringComparison.OrdinalIgnoreCase) || Type.Equals("urltable", StringComparison.OrdinalIgnoreCase)) && UpdateFrequency.HasValue) - { - alias.UpdateFrequency = UpdateFrequency.Value.ToString(); - } - - var createTask = Task.Run(async () => await aliasService.CreateAliasAsync(alias)); - var createResult = createTask.GetAwaiter().GetResult(); - - WriteVerbose($"Created alias with UUID {createResult.Uuid}"); - - // Apply changes if requested - if (Apply.IsPresent) - { - var applyTask = Task.Run(async () => await aliasService.ReconfigureAliasesAsync()); - var applyResult = applyTask.GetAwaiter().GetResult(); - - WriteVerbose($"Alias changes applied: {applyResult.Status}"); - } - - WriteObject(createResult.Uuid); - } - catch (Exception ex) + // Set protocol for port aliases + if (Type.Equals("port", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(Protocol)) { - HandleException(ex); + alias.Protocol = Protocol.ToUpper(); } + + // Set update frequency for URL aliases + if ((Type.Equals("url", StringComparison.OrdinalIgnoreCase) || Type.Equals("urltable", StringComparison.OrdinalIgnoreCase)) && UpdateFrequency.HasValue) + { + alias.UpdateFrequency = UpdateFrequency.Value.ToString(); + } + + // Use our safe execution method + var createResult = ExecuteAsyncTask(() => aliasService.CreateAliasAsync(alias)); + + // Only continue if no exception occurred + if (ProcessingException != null || createResult == null) + { + return; + } + + WriteVerbose($"Created alias with UUID {createResult.Uuid}"); + + // Apply changes if requested + if (Apply.IsPresent) + { + // Use our safe execution method + var applyResult = ExecuteAsyncTask(() => aliasService.ReconfigureAliasesAsync()); + + // Only continue if no exception occurred + if (ProcessingException != null || applyResult == null) + { + return; + } + + WriteVerbose($"Alias changes applied: {applyResult.Status}"); + } + + WriteObject(createResult.Uuid); } } } diff --git a/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseDHCPOptionCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseDHCPOptionCmdlet.cs index 80e4aaf..a2b8a4d 100644 --- a/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseDHCPOptionCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseDHCPOptionCmdlet.cs @@ -61,40 +61,45 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - protected override void ProcessRecord() + protected override void ProcessRecordInternal() { - try + var dhcpService = new DHCPService(ApiClient, Logger); + + var option = new DHCPOptionConfig { - var dhcpService = new DHCPService(ApiClient, Logger); + Number = Number, + Value = Value, + Type = Type, + Description = Description + }; - var option = new DHCPOptionConfig + // Use our safe execution method + var createResult = ExecuteAsyncTask(() => dhcpService.CreateOptionAsync(Interface, option)); + + // Only continue if no exception occurred + if (ProcessingException != null || createResult == null) + { + return; + } + + WriteVerbose($"Created DHCP option with UUID {createResult.Uuid}"); + + // Apply changes if requested + if (Apply.IsPresent) + { + // Use our safe execution method + var applyResult = ExecuteAsyncTask(() => dhcpService.ApplyChangesAsync()); + + // Only continue if no exception occurred + if (ProcessingException != null || applyResult == null) { - Number = Number, - Value = Value, - Type = Type, - Description = Description - }; - - var createTask = Task.Run(async () => await dhcpService.CreateOptionAsync(Interface, option)); - var createResult = createTask.GetAwaiter().GetResult(); - - WriteVerbose($"Created DHCP option with UUID {createResult.Uuid}"); - - // Apply changes if requested - if (Apply.IsPresent) - { - var applyTask = Task.Run(async () => await dhcpService.ApplyChangesAsync()); - var applyResult = applyTask.GetAwaiter().GetResult(); - - WriteVerbose($"DHCP changes applied: {applyResult.Status}"); + return; } - WriteObject(createResult.Uuid); - } - catch (Exception ex) - { - HandleException(ex); + WriteVerbose($"DHCP changes applied: {applyResult.Status}"); } + + WriteObject(createResult.Uuid); } } } diff --git a/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseDHCPStaticMappingCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseDHCPStaticMappingCmdlet.cs index 15a25c8..a5ea9a0 100644 --- a/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseDHCPStaticMappingCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/NewOPNSenseDHCPStaticMappingCmdlet.cs @@ -66,41 +66,46 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - protected override void ProcessRecord() + protected override void ProcessRecordInternal() { - try + var dhcpService = new DHCPService(ApiClient, Logger); + + var mapping = new DHCPStaticMappingConfig { - var dhcpService = new DHCPService(ApiClient, Logger); + MacAddress = MacAddress, + IpAddress = IpAddress, + Hostname = Hostname, + Description = Description, + Enabled = Enabled.IsPresent ? "1" : "0" + }; - var mapping = new DHCPStaticMappingConfig + // Use our safe execution method + var createResult = ExecuteAsyncTask(() => dhcpService.CreateStaticMappingAsync(Interface, mapping)); + + // Only continue if no exception occurred + if (ProcessingException != null || createResult == null) + { + return; + } + + WriteVerbose($"Created static mapping with UUID {createResult.Uuid}"); + + // Apply the changes if requested + if (Apply.IsPresent) + { + // Use our safe execution method + var applyResult = ExecuteAsyncTask(() => dhcpService.ApplyChangesAsync()); + + // Only continue if no exception occurred + if (ProcessingException != null || applyResult == null) { - MacAddress = MacAddress, - IpAddress = IpAddress, - Hostname = Hostname, - Description = Description, - Enabled = Enabled.IsPresent ? "1" : "0" - }; - - var createTask = Task.Run(async () => await dhcpService.CreateStaticMappingAsync(Interface, mapping)); - var createResult = createTask.GetAwaiter().GetResult(); - - WriteVerbose($"Created static mapping with UUID {createResult.Uuid}"); - - // Apply the changes if requested - if (Apply.IsPresent) - { - var applyTask = Task.Run(async () => await dhcpService.ApplyChangesAsync()); - var applyResult = applyTask.GetAwaiter().GetResult(); - - WriteVerbose($"DHCP changes applied: {applyResult.Status}"); + return; } - WriteObject(createResult.Uuid); - } - catch (Exception ex) - { - HandleException(ex); + WriteVerbose($"DHCP changes applied: {applyResult.Status}"); } + + WriteObject(createResult.Uuid); } } } diff --git a/src/PSOPNSenseAPI/Cmdlets/RestartOPNSenseFirewallCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/RestartOPNSenseFirewallCmdlet.cs index d0b410b..e06872a 100644 --- a/src/PSOPNSenseAPI/Cmdlets/RestartOPNSenseFirewallCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/RestartOPNSenseFirewallCmdlet.cs @@ -55,105 +55,106 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - protected override void ProcessRecord() + protected override void ProcessRecordInternal() { - try + if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Restart")) { - if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Restart")) - { - return; - } - - var systemService = new SystemService(ApiClient, Logger); - - // Store connection information for reconnection - string baseUrl = OPNSenseSession.BaseUrl; - string apiKey = null; - string apiSecret = null; - bool skipCertificateCheck = false; - - // Extract connection information from the current session - if (ApiClient is OPNSenseApiClient client) - { - baseUrl = client.BaseUrl; - apiKey = client.ApiKey; - apiSecret = client.ApiSecret; - skipCertificateCheck = client.SkipCertificateCheck; - } - - // If we couldn't get the credentials, we can't reconnect - if (Wait.IsPresent && (string.IsNullOrEmpty(apiKey) || string.IsNullOrEmpty(apiSecret))) - { - WriteWarning("Could not retrieve API credentials from the current session. Will not attempt to reconnect after restart."); - Wait = false; - } - - // Restart the firewall - var task = Task.Run(async () => await systemService.RebootAsync()); - var result = task.GetAwaiter().GetResult(); - - WriteVerbose($"Firewall restart initiated: {result.Status}"); - WriteObject("Firewall restart initiated. The firewall is now restarting."); - - // Wait for the firewall to come back online if requested - if (Wait.IsPresent) - { - WriteVerbose($"Waiting for firewall to come back online (timeout: {Timeout} seconds, interval: {Interval} seconds)"); - WriteObject("Waiting for firewall to come back online..."); - - // Disconnect the current session - OPNSenseSession.Current?.Dispose(); - OPNSenseSession.Current = null; - - // Wait a bit for the firewall to start rebooting - Thread.Sleep(5000); - - // Try to reconnect - DateTime startTime = DateTime.Now; - bool reconnected = false; - - while (DateTime.Now - startTime < TimeSpan.FromSeconds(Timeout)) - { - try - { - WriteVerbose($"Attempting to reconnect to {baseUrl}..."); - - // Create a new logger - var logger = new PowerShellLogger(this); - - // Create a new API client - var newClient = new OPNSenseApiClient(baseUrl, apiKey, apiSecret, skipCertificateCheck, logger); - - // Try to get the system status - var statusService = new SystemService(newClient, logger); - var statusTask = Task.Run(async () => await statusService.GetStatusAsync()); - var statusResult = statusTask.GetAwaiter().GetResult(); - - // If we get here, the firewall is back online - WriteVerbose("Successfully reconnected to the firewall"); - WriteObject($"Firewall is back online. Uptime: {statusResult.Uptime}"); - - // Set the new session - OPNSenseSession.Current = newClient; - reconnected = true; - break; - } - catch (Exception ex) - { - WriteVerbose($"Reconnection attempt failed: {ex.Message}"); - Thread.Sleep(Interval * 1000); - } - } - - if (!reconnected) - { - WriteWarning($"Timed out waiting for firewall to come back online after {Timeout} seconds"); - } - } + return; } - catch (Exception ex) + + var systemService = new SystemService(ApiClient, Logger); + + // Store connection information for reconnection + string baseUrl = OPNSenseSession.BaseUrl; + string apiKey = null; + string apiSecret = null; + bool skipCertificateCheck = false; + + // Extract connection information from the current session + if (ApiClient is OPNSenseApiClient client) { - HandleException(ex); + baseUrl = client.BaseUrl; + apiKey = client.ApiKey; + apiSecret = client.ApiSecret; + skipCertificateCheck = client.SkipCertificateCheck; + } + + // If we couldn't get the credentials, we can't reconnect + if (Wait.IsPresent && (string.IsNullOrEmpty(apiKey) || string.IsNullOrEmpty(apiSecret))) + { + WriteWarning("Could not retrieve API credentials from the current session. Will not attempt to reconnect after restart."); + Wait = false; + } + + // Restart the firewall + var result = ExecuteAsyncTask(() => systemService.RebootAsync()); + + // Only continue if no exception occurred + if (ProcessingException != null || result == null) + { + return; + } + + WriteVerbose($"Firewall restart initiated: {result.Status}"); + WriteObject("Firewall restart initiated. The firewall is now restarting."); + + // Wait for the firewall to come back online if requested + if (Wait.IsPresent) + { + WriteVerbose($"Waiting for firewall to come back online (timeout: {Timeout} seconds, interval: {Interval} seconds)"); + WriteObject("Waiting for firewall to come back online..."); + + // Disconnect the current session + OPNSenseSession.Current?.Dispose(); + OPNSenseSession.Current = null; + + // Wait a bit for the firewall to start rebooting + Thread.Sleep(5000); + + // Try to reconnect + DateTime startTime = DateTime.Now; + bool reconnected = false; + + while (DateTime.Now - startTime < TimeSpan.FromSeconds(Timeout)) + { + try + { + WriteVerbose($"Attempting to reconnect to {baseUrl}..."); + + // Create a new logger + var logger = new PowerShellLogger(this); + + // Create a new API client + var newClient = new OPNSenseApiClient(baseUrl, apiKey, apiSecret, skipCertificateCheck, logger); + + // Try to get the system status + var statusService = new SystemService(newClient, logger); + + // We need to use Task.Run here because we're creating a new client + // and can't use ExecuteAsyncTask which uses the existing client + var statusTask = Task.Run(async () => await statusService.GetStatusAsync()); + var statusResult = statusTask.GetAwaiter().GetResult(); + + // If we get here, the firewall is back online + WriteVerbose("Successfully reconnected to the firewall"); + WriteObject($"Firewall is back online. Uptime: {statusResult.Uptime}"); + + // Set the new session + OPNSenseSession.Current = newClient; + reconnected = true; + break; + } + catch (Exception ex) + { + WriteVerbose($"Reconnection attempt failed: {ex.Message}"); + Thread.Sleep(Interval * 1000); + } + } + + if (!reconnected) + { + WriteWarning($"Timed out waiting for firewall to come back online after {Timeout} seconds"); + } } } } diff --git a/src/PSOPNSenseAPI/Cmdlets/RestartOPNSenseInterfaceCmdlet.cs b/src/PSOPNSenseAPI/Cmdlets/RestartOPNSenseInterfaceCmdlet.cs index ab0a532..77a2152 100644 --- a/src/PSOPNSenseAPI/Cmdlets/RestartOPNSenseInterfaceCmdlet.cs +++ b/src/PSOPNSenseAPI/Cmdlets/RestartOPNSenseInterfaceCmdlet.cs @@ -34,26 +34,25 @@ namespace PSOPNSenseAPI.Cmdlets /// /// Processes the cmdlet /// - protected override void ProcessRecord() + protected override void ProcessRecordInternal() { - try + if (!Force.IsPresent && !ShouldProcess(Name, "Restart interface")) { - if (!Force.IsPresent && !ShouldProcess(Name, "Restart interface")) - { - return; - } - - var interfaceService = new InterfaceService(ApiClient, Logger); - - var task = Task.Run(async () => await interfaceService.RestartInterfaceAsync(Name)); - var result = task.GetAwaiter().GetResult(); - - WriteVerbose($"Interface {Name} restarted: {result.Status}"); + return; } - catch (Exception ex) + + var interfaceService = new InterfaceService(ApiClient, Logger); + + // Use our safe execution method + var result = ExecuteAsyncTask(() => interfaceService.RestartInterfaceAsync(Name)); + + // Only continue if no exception occurred + if (ProcessingException != null || result == null) { - HandleException(ex); + return; } + + WriteVerbose($"Interface {Name} restarted: {result.Status}"); } } } diff --git a/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 b/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 index ff0ec85..6480f15 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.1253' + ModuleVersion = '2025.04.15.1257' # Supported PSEditions CompatiblePSEditions = @('Desktop', 'Core')