13 Commits

Author SHA1 Message Date
GraceSolutions 8731b7303f Add verbose logging of API endpoint URLs 2025-04-18 10:30:58 -04:00
GraceSolutions 5330e620b2 Add release folder to .gitignore 2025-04-18 10:18:01 -04:00
GraceSolutions 5c845b1be6 Implement centralized API endpoint mapping and fix DHCP API paths 2025-04-18 10:15:53 -04:00
GraceSolutions a413875058 Update Test-OPNSenseThreading cmdlet to work without connection (v2025.04.15.1749) 2025-04-15 17:50:27 -04:00
GraceSolutions 459940c32f Add Test-OPNSenseThreading cmdlet to exported cmdlets (v2025.04.15.1740) 2025-04-15 17:41:32 -04:00
GraceSolutions 4eb68f76b7 Add test cmdlet for diagnosing threading issues (v2025.04.15.1714) 2025-04-15 17:15:41 -04:00
GraceSolutions 09de38898d Fix threading issues in PowerShell cmdlets (v2025.04.15.1543) 2025-04-15 16:50:35 -04:00
GraceSolutions 570860bdc2 Fixed more threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread 2025-04-15 12:58:23 -04:00
GraceSolutions 29b83fbf16 Fixed more threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread 2025-04-15 12:53:47 -04:00
GraceSolutions 68fcec516e Fixed more threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread 2025-04-15 12:49:33 -04:00
GraceSolutions 04b0fd5550 Fixed additional threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread 2025-04-15 12:45:29 -04:00
GraceSolutions 2b31bf23a9 Fixed more threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread 2025-04-15 12:34:43 -04:00
GraceSolutions 9c4d3f2eb3 Fixed threading issues in PowerShell cmdlets to ensure WriteObject and WriteError are only called from the main thread 2025-04-15 12:29:12 -04:00
146 changed files with 6721 additions and 3352 deletions
+1
View File
@@ -3,3 +3,4 @@ bin/
obj/
*.zip
output/*.zip
release/
+1 -1
View File
@@ -3,7 +3,7 @@
RootModule = 'lib\PSOPNSenseAPI.dll'
# Version number of this module.
ModuleVersion = '2025.04.15.1143'
ModuleVersion = '2025.04.15.1216'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 PSOPNSenseAPI Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,213 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'lib\PSOPNSenseAPI.dll'
# Version number of this module.
ModuleVersion = '2025.04.15.1543'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
# ID used to uniquely identify this module
GUID = '9a3b4c55-5f9a-4b8c-87d9-9a7a5cdd5c9f'
# Author of this module
Author = 'PSOPNSenseAPI Contributors'
# Company or vendor of this module
CompanyName = 'PSOPNSenseAPI'
# Copyright statement for this module
Copyright = '(c) 2025 PSOPNSenseAPI Contributors. All rights reserved.'
# Description of the functionality provided by this module
Description = 'PowerShell module for interacting with the OPNSense API to configure firewalls'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.1'
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = '4.7.2'
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
ClrVersion = '4.0'
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @()
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @(
'Apply-OPNSenseFirewallChanges',
'Connect-OPNSense',
'Connect-OPNSenseTailscale',
'ConvertTo-OPNSenseNetworkNotation',
'Disable-OPNSenseCronJob',
'Disable-OPNSenseFirewallRule',
'Disable-OPNSensePlugin',
'Disable-OPNSenseTailscale',
'Disconnect-OPNSense',
'Disconnect-OPNSenseTailscale',
'Enable-OPNSenseCronJob',
'Enable-OPNSenseFirewallRule',
'Enable-OPNSensePlugin',
'Enable-OPNSenseTailscale',
'Export-OPNSenseConfig',
'Get-OPNSenseAlias',
'Get-OPNSenseConfig',
'Get-OPNSenseConfigBackup',
'Get-OPNSenseConnection',
'Get-OPNSenseCronJob',
'Get-OPNSenseDHCPLease',
'Get-OPNSenseDHCPOption',
'Get-OPNSenseDHCPServer',
'Get-OPNSenseDHCPStaticMapping',
'Get-OPNSenseDNSForwarding',
'Get-OPNSenseDNSForwardingHost',
'Get-OPNSenseDNSOverride',
'Get-OPNSenseDNSServer',
'Get-OPNSenseFirewallRule',
'Get-OPNSenseFirmware',
'Get-OPNSenseGateway',
'Get-OPNSenseInterface',
'Get-OPNSenseInterfaceStatistics',
'Get-OPNSensePlugin',
'Get-OPNSensePortForwardingRule',
'Get-OPNSenseRoute',
'Get-OPNSenseSystemDNS',
'Get-OPNSenseTailscaleStatus',
'Get-OPNSenseUser',
'Get-OPNSenseVLAN',
'Import-OPNSenseConfig',
'Install-OPNSensePlugin',
'Invoke-OPNSenseNetworkCalculation',
'New-OPNSenseAlias',
'New-OPNSenseCronJob',
'New-OPNSenseDHCPOption',
'New-OPNSenseDHCPStaticMapping',
'New-OPNSenseDNSForwardingHost',
'New-OPNSenseDNSOverride',
'New-OPNSenseFirewallRule',
'New-OPNSenseGateway',
'New-OPNSensePortForwardingRule',
'New-OPNSenseRoute',
'New-OPNSenseSubnetVLANs',
'New-OPNSenseUser',
'New-OPNSenseVLAN',
'Remove-OPNSenseAlias',
'Remove-OPNSenseCronJob',
'Remove-OPNSenseDHCPLease',
'Remove-OPNSenseDHCPOption',
'Remove-OPNSenseDHCPStaticMapping',
'Remove-OPNSenseDNSForwardingHost',
'Remove-OPNSenseDNSOverride',
'Remove-OPNSenseFirewallRule',
'Remove-OPNSenseGateway',
'Remove-OPNSensePortForwardingRule',
'Remove-OPNSenseRoute',
'Remove-OPNSenseUser',
'Remove-OPNSenseVLAN',
'Restart-OPNSenseFirewall',
'Restart-OPNSenseInterface',
'Restore-OPNSenseConfig',
'Set-OPNSenseAlias',
'Set-OPNSenseCronJob',
'Set-OPNSenseDHCPOption',
'Set-OPNSenseDHCPServer',
'Set-OPNSenseDHCPStaticMapping',
'Set-OPNSenseDNSForwarding',
'Set-OPNSenseDNSForwardingHost',
'Set-OPNSenseDNSServer',
'Set-OPNSenseFirewallRule',
'Set-OPNSenseGateway',
'Set-OPNSenseInterface',
'Set-OPNSensePortForwardingRule',
'Set-OPNSenseRoute',
'Set-OPNSenseSystemDNS',
'Set-OPNSenseUser',
'Set-OPNSenseVLAN',
'Uninstall-OPNSensePlugin',
'Update-OPNSenseFirmware',
'Start-OPNSenseFirmwareUpgrade'
)
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
Tags = @('PowerShell', 'OPNSense', 'Firewall', 'API')
# A URL to the license for this module.
LicenseUri = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/LICENSE'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/freedbygrace/PSOPNSenseAPI'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
ReleaseNotes = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/CHANGELOG.md'
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfoURI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,202 @@
# PSOPNSenseAPI
PowerShell module for interacting with the OPNSense API to configure firewalls.
## Overview
PSOPNSenseAPI is a PowerShell module that provides cmdlets for managing OPNSense firewalls through their API. The module is built as a binary module in C# and is compatible with both PowerShell 5.1 and PowerShell 7.
## Features
- Connect to OPNSense firewalls using API credentials
- Manage firewall rules (create, read, update, delete)
- Configure NAT rules and port forwarding
- Manage aliases (host, network, port, URL, etc.)
- Manage network interfaces and VLANs
- Configure DNS settings, overrides, and forwarding
- Configure system DNS servers
- Backup, restore, export, and import configurations
- Manage plugins (install, uninstall, enable, disable)
- Manage users and permissions
- Update and upgrade firmware
- Reboot firewall with wait for reconnection
- Create VLANs from subnet divisions
- Configure DHCP servers, static mappings, leases, and options
- Manage cron jobs
- Configure and manage Tailscale VPN (with auto-installation)
- Manage gateways and static routes
- Network calculation utilities (subnet, supernet, CIDR conversion)
- Apply and revert configuration changes
- Extensive logging and error handling
- Compatible with both PowerShell 5.1 and PowerShell 7
## Requirements
- PowerShell 5.1 or PowerShell 7+
- .NET Framework 4.7.2+ (for PowerShell 5.1)
- .NET Core 3.1+ (for PowerShell 7)
## Installation
```powershell
# Install from PowerShell Gallery (when published)
Install-Module -Name PSOPNSenseAPI
# Or install manually
# 1. Download the module
# 2. Extract to a directory in your PSModulePath
# 3. Import the module
Import-Module PSOPNSenseAPI
```
## Quick Start
```powershell
# Connect to an OPNSense firewall
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
# Get all firewall rules
Get-OPNSenseFirewallRule
# Create a new firewall rule
New-OPNSenseFirewallRule -Description "Allow HTTP" -Protocol TCP -SourceNet "192.168.1.0/24" -DestinationPort 80 -Action Pass
# Apply changes
Apply-OPNSenseFirewallChanges
# Manage aliases
Get-OPNSenseAlias
New-OPNSenseAlias -Name "WebServers" -Type host -Content "192.168.1.10,192.168.1.11" -Description "Web Servers" -Apply
New-OPNSenseAlias -Name "WebPorts" -Type port -Content "80,443" -Protocol TCP -Description "Web Ports" -Apply
Set-OPNSenseAlias -Uuid "9e4ec4f0-9dd1-4fa3-8c1d-8a8e9d772b0f" -Content "192.168.1.10,192.168.1.11,192.168.1.12" -Apply
# Manage interfaces
Get-OPNSenseInterface
Set-OPNSenseInterface -Name "lan" -Description "Local Network" -IpAddress "192.168.1.1" -SubnetMask "24"
# Create VLANs from subnet divisions
New-OPNSenseSubnetVLANs -ParentInterface "em0" -Network "192.168.0.0/24" -SubnetMaskBits 27 -StartingVlanId 10 -VlanIdIncrement 10 -EnableDHCP
# Configure DNS
Set-OPNSenseDNSServer -Forwarding -Forwarders "8.8.8.8","8.8.4.4" -Apply
New-OPNSenseDNSOverride -Hostname "server" -Domain "local" -IpAddress "192.168.1.10"
# Manage plugins
Get-OPNSensePlugin -Installed
Install-OPNSensePlugin -Name "os-acme-client" -Wait
# Manage users
New-OPNSenseUser -Username "john" -Password "P@ssw0rd" -FullName "John Doe" -Groups "admins"
# Configure DHCP
Get-OPNSenseDHCPServer
Set-OPNSenseDHCPServer -Interface "lan" -RangeFrom "192.168.1.100" -RangeTo "192.168.1.200" -Apply
New-OPNSenseDHCPStaticMapping -Interface "lan" -MacAddress "00:11:22:33:44:55" -IpAddress "192.168.1.50" -Hostname "printer"
Get-OPNSenseDHCPLease
Remove-OPNSenseDHCPLease -MacAddress "00:11:22:33:44:55" -Apply
Get-OPNSenseDHCPOption -Interface "lan"
New-OPNSenseDHCPOption -Interface "lan" -Number 66 -Value "192.168.1.10" -Description "TFTP Server" -Apply
# Manage cron jobs
Get-OPNSenseCronJob
New-OPNSenseCronJob -Description "Daily backup" -Command "/usr/local/bin/backup.sh" -Minutes "0" -Hours "2" -Apply
# Configure and manage Tailscale
Get-OPNSenseTailscaleStatus -IncludeInterfaces
Enable-OPNSenseTailscale -AcceptDns -AcceptRoutes -Force
# Advertise subnet routes to Tailscale network
Enable-OPNSenseTailscale -AdvertiseRoutes -SubnetRoutes "192.168.1.0/24","10.0.0.0/8" -Force
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -InstallIfMissing -Force
# Manage DNS forwarding
Get-OPNSenseDNSForwarding
Set-OPNSenseDNSForwarding -Enabled -DnsServers "8.8.8.8","8.8.4.4" -Apply
New-OPNSenseDNSForwardingHost -Domain "example.com" -Server "192.168.1.10" -Apply
# Configure system DNS
Get-OPNSenseSystemDNS
Set-OPNSenseSystemDNS -Hostname "firewall" -Domain "example.com" -DnsServers "1.1.1.1","1.0.0.1" -Apply
# Manage gateways and routes
Get-OPNSenseGateway -IncludeStatus
New-OPNSenseGateway -Name "WAN2_GW" -Interface "opt1" -IpAddress "203.0.113.1" -Description "Secondary WAN" -Apply
Get-OPNSenseRoute
New-OPNSenseRoute -Network "192.168.100.0/24" -Gateway "WAN2_GW" -Description "Remote Office" -Apply
# Network utilities
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Info
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/16" -Operation Subnet -PrefixLength 24
ConvertTo-OPNSenseNetworkNotation -CIDR 24 # Returns "255.255.255.0"
# Advanced supernetting
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Supernet -AdditionalNetworks "192.168.2.0/24"
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/24" -Operation SupernetSummarize -AdditionalNetworks "10.0.1.0/24","10.0.2.0/24"
# Firmware management
Update-OPNSenseFirmware -Wait
# Major firmware upgrade
Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200
# Reboot firewall
Restart-OPNSenseFirewall -Wait -Timeout 300
# Configuration management
Get-OPNSenseConfig | Restore-OPNSenseConfig -Force # Get and restore configuration in one line
$config = Get-OPNSenseConfig # Get configuration as XML document
Export-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml")
Import-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml") -Force
```
## Documentation
For detailed documentation, see the [docs](./docs) directory or use PowerShell's built-in help:
```powershell
Get-Help Connect-OPNSense -Full
```
## Development
### Build and Test
To build and test the module locally:
```powershell
# Run tests
.\build\test.ps1
# Build the module
.\build\build.ps1
# Build a release version with automatic versioning
.\build\build-release.ps1 -Clean -Test -Package
```
### Versioning
The module uses a versioning scheme of `yyyy.MM.dd.HHmm` for releases, which is automatically generated during the build process.
### CI/CD
The project uses GitHub Actions for continuous integration and deployment:
- Builds and tests are run on every push to the main branch
- Release packages are automatically created with the versioning scheme
- Release artifacts are uploaded to GitHub Releases
## Contributing
Contributions are welcome! Please follow these steps:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Run tests to ensure they pass
5. Commit your changes (`git commit -m 'Add some amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 PSOPNSenseAPI Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,213 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'lib\PSOPNSenseAPI.dll'
# Version number of this module.
ModuleVersion = '2025.04.15.1714'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
# ID used to uniquely identify this module
GUID = '9a3b4c55-5f9a-4b8c-87d9-9a7a5cdd5c9f'
# Author of this module
Author = 'PSOPNSenseAPI Contributors'
# Company or vendor of this module
CompanyName = 'PSOPNSenseAPI'
# Copyright statement for this module
Copyright = '(c) 2025 PSOPNSenseAPI Contributors. All rights reserved.'
# Description of the functionality provided by this module
Description = 'PowerShell module for interacting with the OPNSense API to configure firewalls'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.1'
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = '4.7.2'
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
ClrVersion = '4.0'
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @()
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @(
'Apply-OPNSenseFirewallChanges',
'Connect-OPNSense',
'Connect-OPNSenseTailscale',
'ConvertTo-OPNSenseNetworkNotation',
'Disable-OPNSenseCronJob',
'Disable-OPNSenseFirewallRule',
'Disable-OPNSensePlugin',
'Disable-OPNSenseTailscale',
'Disconnect-OPNSense',
'Disconnect-OPNSenseTailscale',
'Enable-OPNSenseCronJob',
'Enable-OPNSenseFirewallRule',
'Enable-OPNSensePlugin',
'Enable-OPNSenseTailscale',
'Export-OPNSenseConfig',
'Get-OPNSenseAlias',
'Get-OPNSenseConfig',
'Get-OPNSenseConfigBackup',
'Get-OPNSenseConnection',
'Get-OPNSenseCronJob',
'Get-OPNSenseDHCPLease',
'Get-OPNSenseDHCPOption',
'Get-OPNSenseDHCPServer',
'Get-OPNSenseDHCPStaticMapping',
'Get-OPNSenseDNSForwarding',
'Get-OPNSenseDNSForwardingHost',
'Get-OPNSenseDNSOverride',
'Get-OPNSenseDNSServer',
'Get-OPNSenseFirewallRule',
'Get-OPNSenseFirmware',
'Get-OPNSenseGateway',
'Get-OPNSenseInterface',
'Get-OPNSenseInterfaceStatistics',
'Get-OPNSensePlugin',
'Get-OPNSensePortForwardingRule',
'Get-OPNSenseRoute',
'Get-OPNSenseSystemDNS',
'Get-OPNSenseTailscaleStatus',
'Get-OPNSenseUser',
'Get-OPNSenseVLAN',
'Import-OPNSenseConfig',
'Install-OPNSensePlugin',
'Invoke-OPNSenseNetworkCalculation',
'New-OPNSenseAlias',
'New-OPNSenseCronJob',
'New-OPNSenseDHCPOption',
'New-OPNSenseDHCPStaticMapping',
'New-OPNSenseDNSForwardingHost',
'New-OPNSenseDNSOverride',
'New-OPNSenseFirewallRule',
'New-OPNSenseGateway',
'New-OPNSensePortForwardingRule',
'New-OPNSenseRoute',
'New-OPNSenseSubnetVLANs',
'New-OPNSenseUser',
'New-OPNSenseVLAN',
'Remove-OPNSenseAlias',
'Remove-OPNSenseCronJob',
'Remove-OPNSenseDHCPLease',
'Remove-OPNSenseDHCPOption',
'Remove-OPNSenseDHCPStaticMapping',
'Remove-OPNSenseDNSForwardingHost',
'Remove-OPNSenseDNSOverride',
'Remove-OPNSenseFirewallRule',
'Remove-OPNSenseGateway',
'Remove-OPNSensePortForwardingRule',
'Remove-OPNSenseRoute',
'Remove-OPNSenseUser',
'Remove-OPNSenseVLAN',
'Restart-OPNSenseFirewall',
'Restart-OPNSenseInterface',
'Restore-OPNSenseConfig',
'Set-OPNSenseAlias',
'Set-OPNSenseCronJob',
'Set-OPNSenseDHCPOption',
'Set-OPNSenseDHCPServer',
'Set-OPNSenseDHCPStaticMapping',
'Set-OPNSenseDNSForwarding',
'Set-OPNSenseDNSForwardingHost',
'Set-OPNSenseDNSServer',
'Set-OPNSenseFirewallRule',
'Set-OPNSenseGateway',
'Set-OPNSenseInterface',
'Set-OPNSensePortForwardingRule',
'Set-OPNSenseRoute',
'Set-OPNSenseSystemDNS',
'Set-OPNSenseUser',
'Set-OPNSenseVLAN',
'Uninstall-OPNSensePlugin',
'Update-OPNSenseFirmware',
'Start-OPNSenseFirmwareUpgrade'
)
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
Tags = @('PowerShell', 'OPNSense', 'Firewall', 'API')
# A URL to the license for this module.
LicenseUri = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/LICENSE'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/freedbygrace/PSOPNSenseAPI'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
ReleaseNotes = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/CHANGELOG.md'
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfoURI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,202 @@
# PSOPNSenseAPI
PowerShell module for interacting with the OPNSense API to configure firewalls.
## Overview
PSOPNSenseAPI is a PowerShell module that provides cmdlets for managing OPNSense firewalls through their API. The module is built as a binary module in C# and is compatible with both PowerShell 5.1 and PowerShell 7.
## Features
- Connect to OPNSense firewalls using API credentials
- Manage firewall rules (create, read, update, delete)
- Configure NAT rules and port forwarding
- Manage aliases (host, network, port, URL, etc.)
- Manage network interfaces and VLANs
- Configure DNS settings, overrides, and forwarding
- Configure system DNS servers
- Backup, restore, export, and import configurations
- Manage plugins (install, uninstall, enable, disable)
- Manage users and permissions
- Update and upgrade firmware
- Reboot firewall with wait for reconnection
- Create VLANs from subnet divisions
- Configure DHCP servers, static mappings, leases, and options
- Manage cron jobs
- Configure and manage Tailscale VPN (with auto-installation)
- Manage gateways and static routes
- Network calculation utilities (subnet, supernet, CIDR conversion)
- Apply and revert configuration changes
- Extensive logging and error handling
- Compatible with both PowerShell 5.1 and PowerShell 7
## Requirements
- PowerShell 5.1 or PowerShell 7+
- .NET Framework 4.7.2+ (for PowerShell 5.1)
- .NET Core 3.1+ (for PowerShell 7)
## Installation
```powershell
# Install from PowerShell Gallery (when published)
Install-Module -Name PSOPNSenseAPI
# Or install manually
# 1. Download the module
# 2. Extract to a directory in your PSModulePath
# 3. Import the module
Import-Module PSOPNSenseAPI
```
## Quick Start
```powershell
# Connect to an OPNSense firewall
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
# Get all firewall rules
Get-OPNSenseFirewallRule
# Create a new firewall rule
New-OPNSenseFirewallRule -Description "Allow HTTP" -Protocol TCP -SourceNet "192.168.1.0/24" -DestinationPort 80 -Action Pass
# Apply changes
Apply-OPNSenseFirewallChanges
# Manage aliases
Get-OPNSenseAlias
New-OPNSenseAlias -Name "WebServers" -Type host -Content "192.168.1.10,192.168.1.11" -Description "Web Servers" -Apply
New-OPNSenseAlias -Name "WebPorts" -Type port -Content "80,443" -Protocol TCP -Description "Web Ports" -Apply
Set-OPNSenseAlias -Uuid "9e4ec4f0-9dd1-4fa3-8c1d-8a8e9d772b0f" -Content "192.168.1.10,192.168.1.11,192.168.1.12" -Apply
# Manage interfaces
Get-OPNSenseInterface
Set-OPNSenseInterface -Name "lan" -Description "Local Network" -IpAddress "192.168.1.1" -SubnetMask "24"
# Create VLANs from subnet divisions
New-OPNSenseSubnetVLANs -ParentInterface "em0" -Network "192.168.0.0/24" -SubnetMaskBits 27 -StartingVlanId 10 -VlanIdIncrement 10 -EnableDHCP
# Configure DNS
Set-OPNSenseDNSServer -Forwarding -Forwarders "8.8.8.8","8.8.4.4" -Apply
New-OPNSenseDNSOverride -Hostname "server" -Domain "local" -IpAddress "192.168.1.10"
# Manage plugins
Get-OPNSensePlugin -Installed
Install-OPNSensePlugin -Name "os-acme-client" -Wait
# Manage users
New-OPNSenseUser -Username "john" -Password "P@ssw0rd" -FullName "John Doe" -Groups "admins"
# Configure DHCP
Get-OPNSenseDHCPServer
Set-OPNSenseDHCPServer -Interface "lan" -RangeFrom "192.168.1.100" -RangeTo "192.168.1.200" -Apply
New-OPNSenseDHCPStaticMapping -Interface "lan" -MacAddress "00:11:22:33:44:55" -IpAddress "192.168.1.50" -Hostname "printer"
Get-OPNSenseDHCPLease
Remove-OPNSenseDHCPLease -MacAddress "00:11:22:33:44:55" -Apply
Get-OPNSenseDHCPOption -Interface "lan"
New-OPNSenseDHCPOption -Interface "lan" -Number 66 -Value "192.168.1.10" -Description "TFTP Server" -Apply
# Manage cron jobs
Get-OPNSenseCronJob
New-OPNSenseCronJob -Description "Daily backup" -Command "/usr/local/bin/backup.sh" -Minutes "0" -Hours "2" -Apply
# Configure and manage Tailscale
Get-OPNSenseTailscaleStatus -IncludeInterfaces
Enable-OPNSenseTailscale -AcceptDns -AcceptRoutes -Force
# Advertise subnet routes to Tailscale network
Enable-OPNSenseTailscale -AdvertiseRoutes -SubnetRoutes "192.168.1.0/24","10.0.0.0/8" -Force
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -InstallIfMissing -Force
# Manage DNS forwarding
Get-OPNSenseDNSForwarding
Set-OPNSenseDNSForwarding -Enabled -DnsServers "8.8.8.8","8.8.4.4" -Apply
New-OPNSenseDNSForwardingHost -Domain "example.com" -Server "192.168.1.10" -Apply
# Configure system DNS
Get-OPNSenseSystemDNS
Set-OPNSenseSystemDNS -Hostname "firewall" -Domain "example.com" -DnsServers "1.1.1.1","1.0.0.1" -Apply
# Manage gateways and routes
Get-OPNSenseGateway -IncludeStatus
New-OPNSenseGateway -Name "WAN2_GW" -Interface "opt1" -IpAddress "203.0.113.1" -Description "Secondary WAN" -Apply
Get-OPNSenseRoute
New-OPNSenseRoute -Network "192.168.100.0/24" -Gateway "WAN2_GW" -Description "Remote Office" -Apply
# Network utilities
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Info
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/16" -Operation Subnet -PrefixLength 24
ConvertTo-OPNSenseNetworkNotation -CIDR 24 # Returns "255.255.255.0"
# Advanced supernetting
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Supernet -AdditionalNetworks "192.168.2.0/24"
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/24" -Operation SupernetSummarize -AdditionalNetworks "10.0.1.0/24","10.0.2.0/24"
# Firmware management
Update-OPNSenseFirmware -Wait
# Major firmware upgrade
Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200
# Reboot firewall
Restart-OPNSenseFirewall -Wait -Timeout 300
# Configuration management
Get-OPNSenseConfig | Restore-OPNSenseConfig -Force # Get and restore configuration in one line
$config = Get-OPNSenseConfig # Get configuration as XML document
Export-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml")
Import-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml") -Force
```
## Documentation
For detailed documentation, see the [docs](./docs) directory or use PowerShell's built-in help:
```powershell
Get-Help Connect-OPNSense -Full
```
## Development
### Build and Test
To build and test the module locally:
```powershell
# Run tests
.\build\test.ps1
# Build the module
.\build\build.ps1
# Build a release version with automatic versioning
.\build\build-release.ps1 -Clean -Test -Package
```
### Versioning
The module uses a versioning scheme of `yyyy.MM.dd.HHmm` for releases, which is automatically generated during the build process.
### CI/CD
The project uses GitHub Actions for continuous integration and deployment:
- Builds and tests are run on every push to the main branch
- Release packages are automatically created with the versioning scheme
- Release artifacts are uploaded to GitHub Releases
## Contributing
Contributions are welcome! Please follow these steps:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Run tests to ensure they pass
5. Commit your changes (`git commit -m 'Add some amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 PSOPNSenseAPI Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,214 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'lib\PSOPNSenseAPI.dll'
# Version number of this module.
ModuleVersion = '2025.04.15.1740'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
# ID used to uniquely identify this module
GUID = '9a3b4c55-5f9a-4b8c-87d9-9a7a5cdd5c9f'
# Author of this module
Author = 'PSOPNSenseAPI Contributors'
# Company or vendor of this module
CompanyName = 'PSOPNSenseAPI'
# Copyright statement for this module
Copyright = '(c) 2025 PSOPNSenseAPI Contributors. All rights reserved.'
# Description of the functionality provided by this module
Description = 'PowerShell module for interacting with the OPNSense API to configure firewalls'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.1'
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = '4.7.2'
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
ClrVersion = '4.0'
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @()
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @(
'Apply-OPNSenseFirewallChanges',
'Connect-OPNSense',
'Connect-OPNSenseTailscale',
'ConvertTo-OPNSenseNetworkNotation',
'Disable-OPNSenseCronJob',
'Disable-OPNSenseFirewallRule',
'Disable-OPNSensePlugin',
'Disable-OPNSenseTailscale',
'Disconnect-OPNSense',
'Disconnect-OPNSenseTailscale',
'Enable-OPNSenseCronJob',
'Enable-OPNSenseFirewallRule',
'Enable-OPNSensePlugin',
'Enable-OPNSenseTailscale',
'Export-OPNSenseConfig',
'Get-OPNSenseAlias',
'Get-OPNSenseConfig',
'Get-OPNSenseConfigBackup',
'Get-OPNSenseConnection',
'Get-OPNSenseCronJob',
'Get-OPNSenseDHCPLease',
'Get-OPNSenseDHCPOption',
'Get-OPNSenseDHCPServer',
'Get-OPNSenseDHCPStaticMapping',
'Get-OPNSenseDNSForwarding',
'Get-OPNSenseDNSForwardingHost',
'Get-OPNSenseDNSOverride',
'Get-OPNSenseDNSServer',
'Get-OPNSenseFirewallRule',
'Get-OPNSenseFirmware',
'Get-OPNSenseGateway',
'Get-OPNSenseInterface',
'Get-OPNSenseInterfaceStatistics',
'Get-OPNSensePlugin',
'Get-OPNSensePortForwardingRule',
'Get-OPNSenseRoute',
'Get-OPNSenseSystemDNS',
'Get-OPNSenseTailscaleStatus',
'Get-OPNSenseUser',
'Get-OPNSenseVLAN',
'Import-OPNSenseConfig',
'Install-OPNSensePlugin',
'Invoke-OPNSenseNetworkCalculation',
'New-OPNSenseAlias',
'New-OPNSenseCronJob',
'New-OPNSenseDHCPOption',
'New-OPNSenseDHCPStaticMapping',
'New-OPNSenseDNSForwardingHost',
'New-OPNSenseDNSOverride',
'New-OPNSenseFirewallRule',
'New-OPNSenseGateway',
'New-OPNSensePortForwardingRule',
'New-OPNSenseRoute',
'New-OPNSenseSubnetVLANs',
'New-OPNSenseUser',
'New-OPNSenseVLAN',
'Remove-OPNSenseAlias',
'Remove-OPNSenseCronJob',
'Remove-OPNSenseDHCPLease',
'Remove-OPNSenseDHCPOption',
'Remove-OPNSenseDHCPStaticMapping',
'Remove-OPNSenseDNSForwardingHost',
'Remove-OPNSenseDNSOverride',
'Remove-OPNSenseFirewallRule',
'Remove-OPNSenseGateway',
'Remove-OPNSensePortForwardingRule',
'Remove-OPNSenseRoute',
'Remove-OPNSenseUser',
'Remove-OPNSenseVLAN',
'Restart-OPNSenseFirewall',
'Restart-OPNSenseInterface',
'Restore-OPNSenseConfig',
'Set-OPNSenseAlias',
'Set-OPNSenseCronJob',
'Set-OPNSenseDHCPOption',
'Set-OPNSenseDHCPServer',
'Set-OPNSenseDHCPStaticMapping',
'Set-OPNSenseDNSForwarding',
'Set-OPNSenseDNSForwardingHost',
'Set-OPNSenseDNSServer',
'Set-OPNSenseFirewallRule',
'Set-OPNSenseGateway',
'Set-OPNSenseInterface',
'Set-OPNSensePortForwardingRule',
'Set-OPNSenseRoute',
'Set-OPNSenseSystemDNS',
'Set-OPNSenseUser',
'Set-OPNSenseVLAN',
'Uninstall-OPNSensePlugin',
'Update-OPNSenseFirmware',
'Start-OPNSenseFirmwareUpgrade',
'Test-OPNSenseThreading'
)
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
Tags = @('PowerShell', 'OPNSense', 'Firewall', 'API')
# A URL to the license for this module.
LicenseUri = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/LICENSE'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/freedbygrace/PSOPNSenseAPI'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
ReleaseNotes = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/CHANGELOG.md'
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfoURI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,202 @@
# PSOPNSenseAPI
PowerShell module for interacting with the OPNSense API to configure firewalls.
## Overview
PSOPNSenseAPI is a PowerShell module that provides cmdlets for managing OPNSense firewalls through their API. The module is built as a binary module in C# and is compatible with both PowerShell 5.1 and PowerShell 7.
## Features
- Connect to OPNSense firewalls using API credentials
- Manage firewall rules (create, read, update, delete)
- Configure NAT rules and port forwarding
- Manage aliases (host, network, port, URL, etc.)
- Manage network interfaces and VLANs
- Configure DNS settings, overrides, and forwarding
- Configure system DNS servers
- Backup, restore, export, and import configurations
- Manage plugins (install, uninstall, enable, disable)
- Manage users and permissions
- Update and upgrade firmware
- Reboot firewall with wait for reconnection
- Create VLANs from subnet divisions
- Configure DHCP servers, static mappings, leases, and options
- Manage cron jobs
- Configure and manage Tailscale VPN (with auto-installation)
- Manage gateways and static routes
- Network calculation utilities (subnet, supernet, CIDR conversion)
- Apply and revert configuration changes
- Extensive logging and error handling
- Compatible with both PowerShell 5.1 and PowerShell 7
## Requirements
- PowerShell 5.1 or PowerShell 7+
- .NET Framework 4.7.2+ (for PowerShell 5.1)
- .NET Core 3.1+ (for PowerShell 7)
## Installation
```powershell
# Install from PowerShell Gallery (when published)
Install-Module -Name PSOPNSenseAPI
# Or install manually
# 1. Download the module
# 2. Extract to a directory in your PSModulePath
# 3. Import the module
Import-Module PSOPNSenseAPI
```
## Quick Start
```powershell
# Connect to an OPNSense firewall
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
# Get all firewall rules
Get-OPNSenseFirewallRule
# Create a new firewall rule
New-OPNSenseFirewallRule -Description "Allow HTTP" -Protocol TCP -SourceNet "192.168.1.0/24" -DestinationPort 80 -Action Pass
# Apply changes
Apply-OPNSenseFirewallChanges
# Manage aliases
Get-OPNSenseAlias
New-OPNSenseAlias -Name "WebServers" -Type host -Content "192.168.1.10,192.168.1.11" -Description "Web Servers" -Apply
New-OPNSenseAlias -Name "WebPorts" -Type port -Content "80,443" -Protocol TCP -Description "Web Ports" -Apply
Set-OPNSenseAlias -Uuid "9e4ec4f0-9dd1-4fa3-8c1d-8a8e9d772b0f" -Content "192.168.1.10,192.168.1.11,192.168.1.12" -Apply
# Manage interfaces
Get-OPNSenseInterface
Set-OPNSenseInterface -Name "lan" -Description "Local Network" -IpAddress "192.168.1.1" -SubnetMask "24"
# Create VLANs from subnet divisions
New-OPNSenseSubnetVLANs -ParentInterface "em0" -Network "192.168.0.0/24" -SubnetMaskBits 27 -StartingVlanId 10 -VlanIdIncrement 10 -EnableDHCP
# Configure DNS
Set-OPNSenseDNSServer -Forwarding -Forwarders "8.8.8.8","8.8.4.4" -Apply
New-OPNSenseDNSOverride -Hostname "server" -Domain "local" -IpAddress "192.168.1.10"
# Manage plugins
Get-OPNSensePlugin -Installed
Install-OPNSensePlugin -Name "os-acme-client" -Wait
# Manage users
New-OPNSenseUser -Username "john" -Password "P@ssw0rd" -FullName "John Doe" -Groups "admins"
# Configure DHCP
Get-OPNSenseDHCPServer
Set-OPNSenseDHCPServer -Interface "lan" -RangeFrom "192.168.1.100" -RangeTo "192.168.1.200" -Apply
New-OPNSenseDHCPStaticMapping -Interface "lan" -MacAddress "00:11:22:33:44:55" -IpAddress "192.168.1.50" -Hostname "printer"
Get-OPNSenseDHCPLease
Remove-OPNSenseDHCPLease -MacAddress "00:11:22:33:44:55" -Apply
Get-OPNSenseDHCPOption -Interface "lan"
New-OPNSenseDHCPOption -Interface "lan" -Number 66 -Value "192.168.1.10" -Description "TFTP Server" -Apply
# Manage cron jobs
Get-OPNSenseCronJob
New-OPNSenseCronJob -Description "Daily backup" -Command "/usr/local/bin/backup.sh" -Minutes "0" -Hours "2" -Apply
# Configure and manage Tailscale
Get-OPNSenseTailscaleStatus -IncludeInterfaces
Enable-OPNSenseTailscale -AcceptDns -AcceptRoutes -Force
# Advertise subnet routes to Tailscale network
Enable-OPNSenseTailscale -AdvertiseRoutes -SubnetRoutes "192.168.1.0/24","10.0.0.0/8" -Force
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -InstallIfMissing -Force
# Manage DNS forwarding
Get-OPNSenseDNSForwarding
Set-OPNSenseDNSForwarding -Enabled -DnsServers "8.8.8.8","8.8.4.4" -Apply
New-OPNSenseDNSForwardingHost -Domain "example.com" -Server "192.168.1.10" -Apply
# Configure system DNS
Get-OPNSenseSystemDNS
Set-OPNSenseSystemDNS -Hostname "firewall" -Domain "example.com" -DnsServers "1.1.1.1","1.0.0.1" -Apply
# Manage gateways and routes
Get-OPNSenseGateway -IncludeStatus
New-OPNSenseGateway -Name "WAN2_GW" -Interface "opt1" -IpAddress "203.0.113.1" -Description "Secondary WAN" -Apply
Get-OPNSenseRoute
New-OPNSenseRoute -Network "192.168.100.0/24" -Gateway "WAN2_GW" -Description "Remote Office" -Apply
# Network utilities
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Info
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/16" -Operation Subnet -PrefixLength 24
ConvertTo-OPNSenseNetworkNotation -CIDR 24 # Returns "255.255.255.0"
# Advanced supernetting
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Supernet -AdditionalNetworks "192.168.2.0/24"
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/24" -Operation SupernetSummarize -AdditionalNetworks "10.0.1.0/24","10.0.2.0/24"
# Firmware management
Update-OPNSenseFirmware -Wait
# Major firmware upgrade
Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200
# Reboot firewall
Restart-OPNSenseFirewall -Wait -Timeout 300
# Configuration management
Get-OPNSenseConfig | Restore-OPNSenseConfig -Force # Get and restore configuration in one line
$config = Get-OPNSenseConfig # Get configuration as XML document
Export-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml")
Import-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml") -Force
```
## Documentation
For detailed documentation, see the [docs](./docs) directory or use PowerShell's built-in help:
```powershell
Get-Help Connect-OPNSense -Full
```
## Development
### Build and Test
To build and test the module locally:
```powershell
# Run tests
.\build\test.ps1
# Build the module
.\build\build.ps1
# Build a release version with automatic versioning
.\build\build-release.ps1 -Clean -Test -Package
```
### Versioning
The module uses a versioning scheme of `yyyy.MM.dd.HHmm` for releases, which is automatically generated during the build process.
### CI/CD
The project uses GitHub Actions for continuous integration and deployment:
- Builds and tests are run on every push to the main branch
- Release packages are automatically created with the versioning scheme
- Release artifacts are uploaded to GitHub Releases
## Contributing
Contributions are welcome! Please follow these steps:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Run tests to ensure they pass
5. Commit your changes (`git commit -m 'Add some amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 PSOPNSenseAPI Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,214 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'lib\PSOPNSenseAPI.dll'
# Version number of this module.
ModuleVersion = '2025.04.15.1749'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
# ID used to uniquely identify this module
GUID = '9a3b4c55-5f9a-4b8c-87d9-9a7a5cdd5c9f'
# Author of this module
Author = 'PSOPNSenseAPI Contributors'
# Company or vendor of this module
CompanyName = 'PSOPNSenseAPI'
# Copyright statement for this module
Copyright = '(c) 2025 PSOPNSenseAPI Contributors. All rights reserved.'
# Description of the functionality provided by this module
Description = 'PowerShell module for interacting with the OPNSense API to configure firewalls'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.1'
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = '4.7.2'
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
ClrVersion = '4.0'
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @()
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @(
'Apply-OPNSenseFirewallChanges',
'Connect-OPNSense',
'Connect-OPNSenseTailscale',
'ConvertTo-OPNSenseNetworkNotation',
'Disable-OPNSenseCronJob',
'Disable-OPNSenseFirewallRule',
'Disable-OPNSensePlugin',
'Disable-OPNSenseTailscale',
'Disconnect-OPNSense',
'Disconnect-OPNSenseTailscale',
'Enable-OPNSenseCronJob',
'Enable-OPNSenseFirewallRule',
'Enable-OPNSensePlugin',
'Enable-OPNSenseTailscale',
'Export-OPNSenseConfig',
'Get-OPNSenseAlias',
'Get-OPNSenseConfig',
'Get-OPNSenseConfigBackup',
'Get-OPNSenseConnection',
'Get-OPNSenseCronJob',
'Get-OPNSenseDHCPLease',
'Get-OPNSenseDHCPOption',
'Get-OPNSenseDHCPServer',
'Get-OPNSenseDHCPStaticMapping',
'Get-OPNSenseDNSForwarding',
'Get-OPNSenseDNSForwardingHost',
'Get-OPNSenseDNSOverride',
'Get-OPNSenseDNSServer',
'Get-OPNSenseFirewallRule',
'Get-OPNSenseFirmware',
'Get-OPNSenseGateway',
'Get-OPNSenseInterface',
'Get-OPNSenseInterfaceStatistics',
'Get-OPNSensePlugin',
'Get-OPNSensePortForwardingRule',
'Get-OPNSenseRoute',
'Get-OPNSenseSystemDNS',
'Get-OPNSenseTailscaleStatus',
'Get-OPNSenseUser',
'Get-OPNSenseVLAN',
'Import-OPNSenseConfig',
'Install-OPNSensePlugin',
'Invoke-OPNSenseNetworkCalculation',
'New-OPNSenseAlias',
'New-OPNSenseCronJob',
'New-OPNSenseDHCPOption',
'New-OPNSenseDHCPStaticMapping',
'New-OPNSenseDNSForwardingHost',
'New-OPNSenseDNSOverride',
'New-OPNSenseFirewallRule',
'New-OPNSenseGateway',
'New-OPNSensePortForwardingRule',
'New-OPNSenseRoute',
'New-OPNSenseSubnetVLANs',
'New-OPNSenseUser',
'New-OPNSenseVLAN',
'Remove-OPNSenseAlias',
'Remove-OPNSenseCronJob',
'Remove-OPNSenseDHCPLease',
'Remove-OPNSenseDHCPOption',
'Remove-OPNSenseDHCPStaticMapping',
'Remove-OPNSenseDNSForwardingHost',
'Remove-OPNSenseDNSOverride',
'Remove-OPNSenseFirewallRule',
'Remove-OPNSenseGateway',
'Remove-OPNSensePortForwardingRule',
'Remove-OPNSenseRoute',
'Remove-OPNSenseUser',
'Remove-OPNSenseVLAN',
'Restart-OPNSenseFirewall',
'Restart-OPNSenseInterface',
'Restore-OPNSenseConfig',
'Set-OPNSenseAlias',
'Set-OPNSenseCronJob',
'Set-OPNSenseDHCPOption',
'Set-OPNSenseDHCPServer',
'Set-OPNSenseDHCPStaticMapping',
'Set-OPNSenseDNSForwarding',
'Set-OPNSenseDNSForwardingHost',
'Set-OPNSenseDNSServer',
'Set-OPNSenseFirewallRule',
'Set-OPNSenseGateway',
'Set-OPNSenseInterface',
'Set-OPNSensePortForwardingRule',
'Set-OPNSenseRoute',
'Set-OPNSenseSystemDNS',
'Set-OPNSenseUser',
'Set-OPNSenseVLAN',
'Uninstall-OPNSensePlugin',
'Update-OPNSenseFirmware',
'Start-OPNSenseFirmwareUpgrade',
'Test-OPNSenseThreading'
)
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
Tags = @('PowerShell', 'OPNSense', 'Firewall', 'API')
# A URL to the license for this module.
LicenseUri = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/LICENSE'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/freedbygrace/PSOPNSenseAPI'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
ReleaseNotes = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/CHANGELOG.md'
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfoURI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,202 @@
# PSOPNSenseAPI
PowerShell module for interacting with the OPNSense API to configure firewalls.
## Overview
PSOPNSenseAPI is a PowerShell module that provides cmdlets for managing OPNSense firewalls through their API. The module is built as a binary module in C# and is compatible with both PowerShell 5.1 and PowerShell 7.
## Features
- Connect to OPNSense firewalls using API credentials
- Manage firewall rules (create, read, update, delete)
- Configure NAT rules and port forwarding
- Manage aliases (host, network, port, URL, etc.)
- Manage network interfaces and VLANs
- Configure DNS settings, overrides, and forwarding
- Configure system DNS servers
- Backup, restore, export, and import configurations
- Manage plugins (install, uninstall, enable, disable)
- Manage users and permissions
- Update and upgrade firmware
- Reboot firewall with wait for reconnection
- Create VLANs from subnet divisions
- Configure DHCP servers, static mappings, leases, and options
- Manage cron jobs
- Configure and manage Tailscale VPN (with auto-installation)
- Manage gateways and static routes
- Network calculation utilities (subnet, supernet, CIDR conversion)
- Apply and revert configuration changes
- Extensive logging and error handling
- Compatible with both PowerShell 5.1 and PowerShell 7
## Requirements
- PowerShell 5.1 or PowerShell 7+
- .NET Framework 4.7.2+ (for PowerShell 5.1)
- .NET Core 3.1+ (for PowerShell 7)
## Installation
```powershell
# Install from PowerShell Gallery (when published)
Install-Module -Name PSOPNSenseAPI
# Or install manually
# 1. Download the module
# 2. Extract to a directory in your PSModulePath
# 3. Import the module
Import-Module PSOPNSenseAPI
```
## Quick Start
```powershell
# Connect to an OPNSense firewall
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
# Get all firewall rules
Get-OPNSenseFirewallRule
# Create a new firewall rule
New-OPNSenseFirewallRule -Description "Allow HTTP" -Protocol TCP -SourceNet "192.168.1.0/24" -DestinationPort 80 -Action Pass
# Apply changes
Apply-OPNSenseFirewallChanges
# Manage aliases
Get-OPNSenseAlias
New-OPNSenseAlias -Name "WebServers" -Type host -Content "192.168.1.10,192.168.1.11" -Description "Web Servers" -Apply
New-OPNSenseAlias -Name "WebPorts" -Type port -Content "80,443" -Protocol TCP -Description "Web Ports" -Apply
Set-OPNSenseAlias -Uuid "9e4ec4f0-9dd1-4fa3-8c1d-8a8e9d772b0f" -Content "192.168.1.10,192.168.1.11,192.168.1.12" -Apply
# Manage interfaces
Get-OPNSenseInterface
Set-OPNSenseInterface -Name "lan" -Description "Local Network" -IpAddress "192.168.1.1" -SubnetMask "24"
# Create VLANs from subnet divisions
New-OPNSenseSubnetVLANs -ParentInterface "em0" -Network "192.168.0.0/24" -SubnetMaskBits 27 -StartingVlanId 10 -VlanIdIncrement 10 -EnableDHCP
# Configure DNS
Set-OPNSenseDNSServer -Forwarding -Forwarders "8.8.8.8","8.8.4.4" -Apply
New-OPNSenseDNSOverride -Hostname "server" -Domain "local" -IpAddress "192.168.1.10"
# Manage plugins
Get-OPNSensePlugin -Installed
Install-OPNSensePlugin -Name "os-acme-client" -Wait
# Manage users
New-OPNSenseUser -Username "john" -Password "P@ssw0rd" -FullName "John Doe" -Groups "admins"
# Configure DHCP
Get-OPNSenseDHCPServer
Set-OPNSenseDHCPServer -Interface "lan" -RangeFrom "192.168.1.100" -RangeTo "192.168.1.200" -Apply
New-OPNSenseDHCPStaticMapping -Interface "lan" -MacAddress "00:11:22:33:44:55" -IpAddress "192.168.1.50" -Hostname "printer"
Get-OPNSenseDHCPLease
Remove-OPNSenseDHCPLease -MacAddress "00:11:22:33:44:55" -Apply
Get-OPNSenseDHCPOption -Interface "lan"
New-OPNSenseDHCPOption -Interface "lan" -Number 66 -Value "192.168.1.10" -Description "TFTP Server" -Apply
# Manage cron jobs
Get-OPNSenseCronJob
New-OPNSenseCronJob -Description "Daily backup" -Command "/usr/local/bin/backup.sh" -Minutes "0" -Hours "2" -Apply
# Configure and manage Tailscale
Get-OPNSenseTailscaleStatus -IncludeInterfaces
Enable-OPNSenseTailscale -AcceptDns -AcceptRoutes -Force
# Advertise subnet routes to Tailscale network
Enable-OPNSenseTailscale -AdvertiseRoutes -SubnetRoutes "192.168.1.0/24","10.0.0.0/8" -Force
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -InstallIfMissing -Force
# Manage DNS forwarding
Get-OPNSenseDNSForwarding
Set-OPNSenseDNSForwarding -Enabled -DnsServers "8.8.8.8","8.8.4.4" -Apply
New-OPNSenseDNSForwardingHost -Domain "example.com" -Server "192.168.1.10" -Apply
# Configure system DNS
Get-OPNSenseSystemDNS
Set-OPNSenseSystemDNS -Hostname "firewall" -Domain "example.com" -DnsServers "1.1.1.1","1.0.0.1" -Apply
# Manage gateways and routes
Get-OPNSenseGateway -IncludeStatus
New-OPNSenseGateway -Name "WAN2_GW" -Interface "opt1" -IpAddress "203.0.113.1" -Description "Secondary WAN" -Apply
Get-OPNSenseRoute
New-OPNSenseRoute -Network "192.168.100.0/24" -Gateway "WAN2_GW" -Description "Remote Office" -Apply
# Network utilities
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Info
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/16" -Operation Subnet -PrefixLength 24
ConvertTo-OPNSenseNetworkNotation -CIDR 24 # Returns "255.255.255.0"
# Advanced supernetting
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Supernet -AdditionalNetworks "192.168.2.0/24"
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/24" -Operation SupernetSummarize -AdditionalNetworks "10.0.1.0/24","10.0.2.0/24"
# Firmware management
Update-OPNSenseFirmware -Wait
# Major firmware upgrade
Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200
# Reboot firewall
Restart-OPNSenseFirewall -Wait -Timeout 300
# Configuration management
Get-OPNSenseConfig | Restore-OPNSenseConfig -Force # Get and restore configuration in one line
$config = Get-OPNSenseConfig # Get configuration as XML document
Export-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml")
Import-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml") -Force
```
## Documentation
For detailed documentation, see the [docs](./docs) directory or use PowerShell's built-in help:
```powershell
Get-Help Connect-OPNSense -Full
```
## Development
### Build and Test
To build and test the module locally:
```powershell
# Run tests
.\build\test.ps1
# Build the module
.\build\build.ps1
# Build a release version with automatic versioning
.\build\build-release.ps1 -Clean -Test -Package
```
### Versioning
The module uses a versioning scheme of `yyyy.MM.dd.HHmm` for releases, which is automatically generated during the build process.
### CI/CD
The project uses GitHub Actions for continuous integration and deployment:
- Builds and tests are run on every push to the main branch
- Release packages are automatically created with the versioning scheme
- Release artifacts are uploaded to GitHub Releases
## Contributing
Contributions are welcome! Please follow these steps:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Run tests to ensure they pass
5. Commit your changes (`git commit -m 'Add some amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
@@ -46,55 +46,70 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var firewallService = new FirewallService(ApiClient, Logger);
var firewallService = new FirewallService(ApiClient, Logger);
if (NoRollback.IsPresent)
if (NoRollback.IsPresent)
{
WriteVerbose("Applying firewall changes without rollback protection");
var applyResult = ExecuteAsyncTask(() => firewallService.ApplyChangesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || applyResult == null)
{
WriteVerbose("Applying firewall changes without rollback protection");
var applyTask = Task.Run(async () => await firewallService.ApplyChangesAsync());
var applyResult = applyTask.GetAwaiter().GetResult();
WriteVerbose($"Firewall changes applied: {applyResult.Status}");
return;
}
WriteVerbose($"Firewall changes applied: {applyResult.Status}");
}
else
{
WriteVerbose("Creating savepoint for rollback protection");
var savepointResult = ExecuteAsyncTask(() => firewallService.CreateSavepointAsync());
// Only continue if no exception occurred
if (ProcessingException != null || savepointResult == null)
{
return;
}
var revision = savepointResult.Revision;
WriteVerbose($"Created savepoint with revision {revision}");
WriteVerbose("Applying firewall changes with rollback protection");
var applyResult = ExecuteAsyncTask(() => firewallService.ApplyChangesAsync(revision));
// Only continue if no exception occurred
if (ProcessingException != null || applyResult == null)
{
return;
}
WriteVerbose($"Firewall changes applied: {applyResult.Status}");
if (CancelRollback.IsPresent)
{
WriteVerbose($"Waiting {Timeout} seconds before cancelling rollback");
System.Threading.Thread.Sleep(Timeout * 1000);
WriteVerbose("Cancelling automatic rollback");
var cancelResult = ExecuteAsyncTask(() => firewallService.CancelRollbackAsync(revision));
// Only continue if no exception occurred
if (ProcessingException != null || cancelResult == null)
{
return;
}
WriteVerbose($"Rollback cancelled: {cancelResult.Status}");
}
else
{
WriteVerbose("Creating savepoint for rollback protection");
var savepointTask = Task.Run(async () => await firewallService.CreateSavepointAsync());
var savepointResult = savepointTask.GetAwaiter().GetResult();
var revision = savepointResult.Revision;
WriteVerbose($"Created savepoint with revision {revision}");
WriteVerbose("Applying firewall changes with rollback protection");
var applyTask = Task.Run(async () => await firewallService.ApplyChangesAsync(revision));
var applyResult = applyTask.GetAwaiter().GetResult();
WriteVerbose($"Firewall changes applied: {applyResult.Status}");
if (CancelRollback.IsPresent)
{
WriteVerbose($"Waiting {Timeout} seconds before cancelling rollback");
System.Threading.Thread.Sleep(Timeout * 1000);
WriteVerbose("Cancelling automatic rollback");
var cancelTask = Task.Run(async () => await firewallService.CancelRollbackAsync(revision));
var cancelResult = cancelTask.GetAwaiter().GetResult();
WriteVerbose($"Rollback cancelled: {cancelResult.Status}");
}
else
{
WriteVerbose($"Automatic rollback will occur in 60 seconds if connectivity is lost");
}
WriteVerbose($"Automatic rollback will occur in 60 seconds if connectivity is lost");
}
}
catch (Exception ex)
{
HandleException(ex);
}
}
}
}
@@ -22,7 +22,7 @@ namespace PSOPNSenseAPI.Cmdlets
/// </example>
/// </summary>
[Cmdlet(VerbsCommunications.Connect, "OPNSense")]
[OutputType(typeof(void))]
[OutputType(typeof(PSObject))]
public class ConnectOPNSenseCmdlet : OPNSenseBaseCmdlet
{
/// <summary>
@@ -64,14 +64,22 @@ namespace PSOPNSenseAPI.Cmdlets
protected override void ProcessRecordInternal()
{
// Check if already connected
if (OPNSenseSession.IsConnected && !Force.IsPresent)
var sessionState = OPNSenseSessionState.Instance;
if (sessionState.ApiClient != null && sessionState.ApiClient.IsConnected && !Force.IsPresent)
{
WriteWarning($"Already connected to {OPNSenseSession.BaseUrl}. Use -Force to reconnect.");
// Only write warning if WarningPreference is not SilentlyContinue
if (MyInvocation.BoundParameters.ContainsKey("WarningAction") ||
!ActionPreference.SilentlyContinue.Equals(SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.Continue)))
{
WriteWarning($"Already connected to {sessionState.ApiClient.BaseUrl}. Use -Force to reconnect.");
}
return;
}
// Dispose existing connection if there is one
OPNSenseSession.Current?.Dispose();
sessionState.ApiClient?.Dispose();
// Create a new logger
var logger = new PowerShellLogger(this);
@@ -80,9 +88,31 @@ namespace PSOPNSenseAPI.Cmdlets
var client = new OPNSenseApiClient(Server, ApiKey, ApiSecret, SkipCertificateCheck.IsPresent, logger);
// Set the current session
OPNSenseSession.Current = client;
sessionState.ApiClient = client;
OPNSenseSession.Current = client; // Keep this for backward compatibility
// Reset API endpoints
sessionState.ResetApiEndpoints();
WriteVerbose($"Connected to OPNSense firewall at {Server}");
// Detect API version
try
{
var apiVersion = sessionState.ApiEndpoints.GetOPNSenseVersion();
WriteVerbose($"Detected OPNSense API version: {apiVersion}");
}
catch (Exception ex)
{
WriteVerbose($"Failed to detect OPNSense API version: {ex.Message}. Using legacy API endpoints.");
}
// Create and return detailed connection info object
var connectionInfoHelper = new OPNSenseConnectionInfo(client, logger);
var connectionInfo = connectionInfoHelper.GetConnectionInfo();
WriteObject(connectionInfo);
}
}
}
@@ -76,178 +76,213 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </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
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);
}
}
}
@@ -57,6 +57,14 @@ namespace PSOPNSenseAPI.Cmdlets
[Parameter(Mandatory = false, ParameterSetName = "FromIPWithSubnetMask")]
public string IPWithSubnetMask { get; set; }
/// <summary>
/// Begins the processing of the cmdlet
/// </summary>
protected override void BeginProcessing()
{
base.BeginProcessing();
}
/// <summary>
/// Processes the cmdlet
/// </summary>
@@ -88,22 +96,16 @@ namespace PSOPNSenseAPI.Cmdlets
string[] parts = IPWithCIDR.Split('/');
if (parts.Length != 2)
{
WriteError(new ErrorRecord(
new ArgumentException($"Invalid IP with CIDR: {IPWithCIDR}. Expected format: 192.168.1.0/24"),
"InvalidIPWithCIDR",
ErrorCategory.InvalidArgument,
null));
// Use WriteWarning instead of WriteError to avoid threading issues
WriteWarning($"Invalid IP with CIDR: {IPWithCIDR}. Expected format: 192.168.1.0/24");
return;
}
string ip = parts[0];
if (!int.TryParse(parts[1], out int cidr))
{
WriteError(new ErrorRecord(
new ArgumentException($"Invalid CIDR: {parts[1]}"),
"InvalidCIDR",
ErrorCategory.InvalidArgument,
null));
// Use WriteWarning instead of WriteError to avoid threading issues
WriteWarning($"Invalid CIDR: {parts[1]}");
return;
}
@@ -118,11 +120,8 @@ namespace PSOPNSenseAPI.Cmdlets
string[] parts = IPWithSubnetMask.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
{
WriteError(new ErrorRecord(
new ArgumentException($"Invalid IP with subnet mask: {IPWithSubnetMask}. Expected format: 192.168.1.0 255.255.255.0"),
"InvalidIPWithSubnetMask",
ErrorCategory.InvalidArgument,
null));
// Use WriteWarning instead of WriteError to avoid threading issues
WriteWarning($"Invalid IP with subnet mask: {IPWithSubnetMask}. Expected format: 192.168.1.0 255.255.255.0");
return;
}
@@ -136,12 +135,17 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (Exception ex)
{
WriteError(new ErrorRecord(
ex,
"ConversionError",
ErrorCategory.InvalidOperation,
null));
// Use WriteWarning instead of WriteError to avoid threading issues
WriteWarning($"Conversion error: {ex.Message}");
}
}
/// <summary>
/// Ends the processing of the cmdlet
/// </summary>
protected override void EndProcessing()
{
base.EndProcessing();
}
}
}
@@ -34,29 +34,34 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var cronService = new CronService(ApiClient, Logger);
// Use our safe execution method
var result = ExecuteAsyncTask(() => cronService.ToggleJobAsync(Uuid, false));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
var cronService = new CronService(ApiClient, Logger);
var task = Task.Run(async () => await cronService.ToggleJobAsync(Uuid, false));
var result = task.GetAwaiter().GetResult();
WriteVerbose($"Cron job {Uuid} disabled: {result.Result}");
// 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;
}
catch (Exception ex)
WriteVerbose($"Cron job {Uuid} disabled: {result.Result}");
// Apply the changes if requested
if (Apply.IsPresent)
{
HandleException(ex);
// Use our safe execution method
var applyResult = ExecuteAsyncTask(() => cronService.ApplyChangesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || applyResult == null)
{
return;
}
WriteVerbose($"Cron changes applied: {applyResult.Status}");
}
}
}
@@ -28,21 +28,20 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var firewallService = new FirewallService(ApiClient, Logger);
var firewallService = new FirewallService(ApiClient, Logger);
var task = Task.Run(async () => await firewallService.ToggleRuleAsync(Uuid, false));
var result = task.GetAwaiter().GetResult();
// Use our safe execution method
var result = ExecuteAsyncTask(() => firewallService.ToggleRuleAsync(Uuid, false));
WriteVerbose($"Firewall rule {Uuid} disabled: {result.Result}");
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteVerbose($"Firewall rule {Uuid} disabled: {result.Result}");
}
}
}
@@ -28,22 +28,21 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var pluginService = new PluginService(ApiClient, Logger);
var pluginService = new PluginService(ApiClient, Logger);
var task = Task.Run(async () => await pluginService.DisablePluginAsync(Name));
var result = task.GetAwaiter().GetResult();
// Use our safe execution method
var result = ExecuteAsyncTask(() => pluginService.DisablePluginAsync(Name));
WriteVerbose($"Plugin {Name} disabled: {result.Status}");
WriteObject($"Plugin {Name} disabled: {result.Status}");
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteVerbose($"Plugin {Name} disabled: {result.Status}");
WriteObject($"Plugin {Name} disabled: {result.Status}");
}
}
}
@@ -38,79 +38,100 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </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
var isInstalledTask = Task.Run(async () => await tailscaleService.IsPluginInstalledAsync());
var isInstalled = isInstalledTask.GetAwaiter().GetResult();
if (!isInstalled)
{
WriteWarning("Tailscale plugin is not installed on the OPNSense firewall.");
return;
}
if (!isInstalled)
{
WriteWarning("Tailscale plugin is not installed on the OPNSense firewall.");
return;
}
// Get current settings
var settingsResult = ExecuteAsyncTask(() => tailscaleService.GetSettingsAsync());
// Get current settings
var settingsTask = Task.Run(async () => await tailscaleService.GetSettingsAsync());
var currentSettings = settingsTask.GetAwaiter().GetResult().General;
// Only continue if no exception occurred
if (ProcessingException != null || settingsResult == null)
{
return;
}
// Create new settings with disabled flag
var settings = new TailscaleSettings
{
Enabled = "0",
AcceptDns = currentSettings.AcceptDns,
AcceptRoutes = currentSettings.AcceptRoutes,
AdvertiseExitNode = currentSettings.AdvertiseExitNode,
AdvertiseRoutes = currentSettings.AdvertiseRoutes,
RoutesToAdvertise = currentSettings.RoutesToAdvertise,
Hostname = currentSettings.Hostname,
LoginServer = currentSettings.LoginServer,
Ssh = currentSettings.Ssh,
Ephemeral = currentSettings.Ephemeral,
ResetOnStart = currentSettings.ResetOnStart
};
var currentSettings = settingsResult.General;
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Disable Tailscale"))
// Create new settings with disabled flag
var settings = new TailscaleSettings
{
Enabled = "0",
AcceptDns = currentSettings.AcceptDns,
AcceptRoutes = currentSettings.AcceptRoutes,
AdvertiseExitNode = currentSettings.AdvertiseExitNode,
AdvertiseRoutes = currentSettings.AdvertiseRoutes,
RoutesToAdvertise = currentSettings.RoutesToAdvertise,
Hostname = currentSettings.Hostname,
LoginServer = currentSettings.LoginServer,
Ssh = currentSettings.Ssh,
Ephemeral = currentSettings.Ephemeral,
ResetOnStart = currentSettings.ResetOnStart
};
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Disable Tailscale"))
{
return;
}
// 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}");
// Stop the service if requested
if (Stop.IsPresent)
{
WriteVerbose("Stopping Tailscale service...");
var stopResult = ExecuteAsyncTask(() => tailscaleService.StopServiceAsync());
// Only continue if no exception occurred
if (ProcessingException != null || stopResult == null)
{
return;
}
// Update settings
var updateTask = Task.Run(async () => await tailscaleService.UpdateSettingsAsync(settings));
var updateResult = updateTask.GetAwaiter().GetResult();
WriteVerbose($"Tailscale settings updated: {updateResult.Result}");
// Stop the service if requested
if (Stop.IsPresent)
{
WriteVerbose("Stopping Tailscale service...");
var stopTask = Task.Run(async () => await tailscaleService.StopServiceAsync());
var stopResult = stopTask.GetAwaiter().GetResult();
WriteVerbose($"Tailscale service stopped: {stopResult.Status}");
}
// Get updated status
var statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
var status = statusTask.GetAwaiter().GetResult();
// Create result object
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("PluginInstalled", true));
result.Properties.Add(new PSNoteProperty("Enabled", status.Enabled));
result.Properties.Add(new PSNoteProperty("Running", status.Running));
result.Properties.Add(new PSNoteProperty("Status", status.Status));
WriteObject(result);
WriteVerbose($"Tailscale service stopped: {stopResult.Status}");
}
catch (Exception ex)
// Get updated status
var status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
// Only continue if no exception occurred
if (ProcessingException != null || status == null)
{
HandleException(ex);
return;
}
// Create result object
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("PluginInstalled", true));
result.Properties.Add(new PSNoteProperty("Enabled", status.Enabled));
result.Properties.Add(new PSNoteProperty("Running", status.Running));
result.Properties.Add(new PSNoteProperty("Status", status.Status));
WriteObject(result);
}
}
}
@@ -17,6 +17,14 @@ namespace PSOPNSenseAPI.Cmdlets
[OutputType(typeof(void))]
public class DisconnectOPNSenseCmdlet : PSCmdlet
{
/// <summary>
/// Begins the processing of the cmdlet
/// </summary>
protected override void BeginProcessing()
{
base.BeginProcessing();
}
/// <summary>
/// Processes the cmdlet
/// </summary>
@@ -38,12 +46,17 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (Exception ex)
{
WriteError(new ErrorRecord(
ex,
"DisconnectionFailed",
ErrorCategory.ConnectionError,
null));
// Use WriteWarning instead of WriteError to avoid threading issues
WriteWarning($"Failed to disconnect: {ex.Message}");
}
}
/// <summary>
/// Ends the processing of the cmdlet
/// </summary>
protected override void EndProcessing()
{
base.EndProcessing();
}
}
}
@@ -27,61 +27,74 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </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);
// Check if the plugin is installed
var isInstalledTask = Task.Run(async () => await tailscaleService.IsPluginInstalledAsync());
var isInstalled = isInstalledTask.GetAwaiter().GetResult();
if (!isInstalled)
{
WriteWarning("Tailscale plugin is not installed on the OPNSense firewall.");
return;
}
// Get current status
var statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
var status = statusTask.GetAwaiter().GetResult();
if (!status.Running)
{
WriteWarning("Tailscale service is not running.");
return;
}
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Disconnect from Tailscale network"))
{
return;
}
// Disconnect from Tailscale
WriteVerbose("Disconnecting from Tailscale network...");
var disconnectTask = Task.Run(async () => await tailscaleService.DisconnectAsync());
var disconnectResult = disconnectTask.GetAwaiter().GetResult();
WriteVerbose($"Tailscale disconnection status: {disconnectResult.Status}");
// Get updated status
statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
status = statusTask.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("DisconnectionStatus", disconnectResult.Status));
WriteObject(result);
return;
}
catch (Exception ex)
if (!isInstalled)
{
HandleException(ex);
WriteWarning("Tailscale plugin is not installed on the OPNSense firewall.");
return;
}
// Get current status
var status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
// Only continue if no exception occurred
if (ProcessingException != null || status == null)
{
return;
}
if (!status.Running)
{
WriteWarning("Tailscale service is not running.");
return;
}
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Disconnect from Tailscale network"))
{
return;
}
// Disconnect from Tailscale
WriteVerbose("Disconnecting from Tailscale network...");
var disconnectResult = ExecuteAsyncTask(() => tailscaleService.DisconnectAsync());
// Only continue if no exception occurred
if (ProcessingException != null || disconnectResult == null)
{
return;
}
WriteVerbose($"Tailscale disconnection status: {disconnectResult.Status}");
// Get updated status
status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
// Only continue if no exception occurred
if (ProcessingException != null || status == 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("DisconnectionStatus", disconnectResult.Status));
WriteObject(result);
}
}
}
@@ -34,29 +34,34 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var cronService = new CronService(ApiClient, Logger);
// Use our safe execution method
var result = ExecuteAsyncTask(() => cronService.ToggleJobAsync(Uuid, true));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
var cronService = new CronService(ApiClient, Logger);
var task = Task.Run(async () => await cronService.ToggleJobAsync(Uuid, true));
var result = task.GetAwaiter().GetResult();
WriteVerbose($"Cron job {Uuid} enabled: {result.Result}");
// 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;
}
catch (Exception ex)
WriteVerbose($"Cron job {Uuid} enabled: {result.Result}");
// Apply the changes if requested
if (Apply.IsPresent)
{
HandleException(ex);
// Use our safe execution method
var applyResult = ExecuteAsyncTask(() => cronService.ApplyChangesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || applyResult == null)
{
return;
}
WriteVerbose($"Cron changes applied: {applyResult.Status}");
}
}
}
@@ -28,21 +28,20 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var firewallService = new FirewallService(ApiClient, Logger);
var firewallService = new FirewallService(ApiClient, Logger);
var task = Task.Run(async () => await firewallService.ToggleRuleAsync(Uuid, true));
var result = task.GetAwaiter().GetResult();
// Use our safe execution method
var result = ExecuteAsyncTask(() => firewallService.ToggleRuleAsync(Uuid, true));
WriteVerbose($"Firewall rule {Uuid} enabled: {result.Result}");
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteVerbose($"Firewall rule {Uuid} enabled: {result.Result}");
}
}
}
@@ -28,22 +28,21 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var pluginService = new PluginService(ApiClient, Logger);
var pluginService = new PluginService(ApiClient, Logger);
var task = Task.Run(async () => await pluginService.EnablePluginAsync(Name));
var result = task.GetAwaiter().GetResult();
// Use our safe execution method
var result = ExecuteAsyncTask(() => pluginService.EnablePluginAsync(Name));
WriteVerbose($"Plugin {Name} enabled: {result.Status}");
WriteObject($"Plugin {Name} enabled: {result.Status}");
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteVerbose($"Plugin {Name} enabled: {result.Status}");
WriteObject($"Plugin {Name} enabled: {result.Status}");
}
}
}
@@ -105,131 +105,164 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </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
var isInstalledTask = Task.Run(async () => await tailscaleService.IsPluginInstalledAsync());
var isInstalled = isInstalledTask.GetAwaiter().GetResult();
if (!isInstalled)
if (!isInstalled)
{
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Install Tailscale plugin"))
{
if (!Force.IsPresent && !ShouldProcess("OPNSense firewall", "Install Tailscale plugin"))
{
WriteWarning("Tailscale plugin is not installed and -Force was not specified. Operation cancelled.");
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.");
WriteWarning("Tailscale plugin is not installed and -Force was not specified. Operation cancelled.");
return;
}
// Get current settings
var settingsTask = Task.Run(async () => await tailscaleService.GetSettingsAsync());
var currentSettings = settingsTask.GetAwaiter().GetResult().General;
WriteVerbose("Tailscale plugin is not installed. Installing...");
var installResult = ExecuteAsyncTask(() => tailscaleService.InstallPluginAsync());
// Process subnet routes if provided
string routesToAdvertise = currentSettings.RoutesToAdvertise;
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.IsPresent)
{
WriteVerbose("Automatically enabling route advertisement because subnet routes were specified.");
AdvertiseRoutes = true;
}
}
// Create new settings
var settings = new TailscaleSettings
{
Enabled = "1",
AcceptDns = AcceptDns.IsPresent ? "1" : "0",
AcceptRoutes = AcceptRoutes.IsPresent ? "1" : "0",
AdvertiseExitNode = AdvertiseExitNode.IsPresent ? "1" : "0",
AdvertiseRoutes = AdvertiseRoutes.IsPresent ? "1" : "0",
RoutesToAdvertise = routesToAdvertise,
Hostname = Hostname ?? currentSettings.Hostname,
LoginServer = LoginServer ?? currentSettings.LoginServer,
Ssh = EnableSsh.IsPresent ? "1" : "0",
Ephemeral = Ephemeral.IsPresent ? "1" : "0",
ResetOnStart = ResetOnStart.IsPresent ? "1" : "0"
};
if (!ShouldProcess("OPNSense firewall", "Enable and configure Tailscale"))
// Only continue if no exception occurred
if (ProcessingException != null)
{
return;
}
// Update settings
var updateTask = Task.Run(async () => await tailscaleService.UpdateSettingsAsync(settings));
var updateResult = updateTask.GetAwaiter().GetResult();
WriteVerbose($"Tailscale settings updated: {updateResult.Result}");
// Get current status
var statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
var status = statusTask.GetAwaiter().GetResult();
// Start or restart the service if requested
if (Restart.IsPresent || (Start.IsPresent && !status.Running))
if (!installResult)
{
if (status.Running)
{
if (Restart.IsPresent)
{
WriteVerbose("Restarting Tailscale service...");
var restartTask = Task.Run(async () => await tailscaleService.RestartServiceAsync());
var restartResult = restartTask.GetAwaiter().GetResult();
WriteVerbose($"Tailscale service restarted: {restartResult.Status}");
}
}
else if (Start.IsPresent)
{
WriteVerbose("Starting Tailscale service...");
var startTask = Task.Run(async () => await tailscaleService.StartServiceAsync());
var startResult = startTask.GetAwaiter().GetResult();
WriteVerbose($"Tailscale service started: {startResult.Status}");
}
ProcessingException = new Exception("Failed to install Tailscale plugin.");
return;
}
// Get updated status
statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
status = statusTask.GetAwaiter().GetResult();
// Create result object
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("PluginInstalled", true));
result.Properties.Add(new PSNoteProperty("Enabled", status.Enabled));
result.Properties.Add(new PSNoteProperty("Running", status.Running));
result.Properties.Add(new PSNoteProperty("Status", status.Status));
result.Properties.Add(new PSNoteProperty("Settings", settings));
WriteObject(result);
WriteVerbose("Tailscale plugin installed successfully.");
}
catch (Exception ex)
// Get current settings
var settingsResult = ExecuteAsyncTask(() => tailscaleService.GetSettingsAsync());
// Only continue if no exception occurred
if (ProcessingException != null || settingsResult == null)
{
HandleException(ex);
return;
}
var currentSettings = settingsResult.General;
// Process subnet routes if provided
string routesToAdvertise = currentSettings.RoutesToAdvertise;
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.IsPresent)
{
WriteVerbose("Automatically enabling route advertisement because subnet routes were specified.");
AdvertiseRoutes = true;
}
}
// Create new settings
var settings = new TailscaleSettings
{
Enabled = "1",
AcceptDns = AcceptDns.IsPresent ? "1" : "0",
AcceptRoutes = AcceptRoutes.IsPresent ? "1" : "0",
AdvertiseExitNode = AdvertiseExitNode.IsPresent ? "1" : "0",
AdvertiseRoutes = AdvertiseRoutes.IsPresent ? "1" : "0",
RoutesToAdvertise = routesToAdvertise,
Hostname = Hostname ?? currentSettings.Hostname,
LoginServer = LoginServer ?? currentSettings.LoginServer,
Ssh = EnableSsh.IsPresent ? "1" : "0",
Ephemeral = Ephemeral.IsPresent ? "1" : "0",
ResetOnStart = ResetOnStart.IsPresent ? "1" : "0"
};
if (!ShouldProcess("OPNSense firewall", "Enable and configure Tailscale"))
{
return;
}
// 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 current status
var status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
// Only continue if no exception occurred
if (ProcessingException != null || status == null)
{
return;
}
// Start or restart the service if requested
if (Restart.IsPresent || (Start.IsPresent && !status.Running))
{
if (status.Running)
{
if (Restart.IsPresent)
{
WriteVerbose("Restarting Tailscale service...");
var restartResult = ExecuteAsyncTask(() => tailscaleService.RestartServiceAsync());
// Only continue if no exception occurred
if (ProcessingException != null || restartResult == null)
{
return;
}
WriteVerbose($"Tailscale service restarted: {restartResult.Status}");
}
}
else if (Start.IsPresent)
{
WriteVerbose("Starting Tailscale service...");
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;
}
// Create result object
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("PluginInstalled", true));
result.Properties.Add(new PSNoteProperty("Enabled", status.Enabled));
result.Properties.Add(new PSNoteProperty("Running", status.Running));
result.Properties.Add(new PSNoteProperty("Status", status.Status));
result.Properties.Add(new PSNoteProperty("Settings", settings));
WriteObject(result);
}
}
}
@@ -35,28 +35,31 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
string fullPath = Path.FullName;
// Check if the file exists
if (File.Exists(fullPath) && !Force.IsPresent)
{
ProcessingException = new IOException($"The file '{fullPath}' already exists. Use -Force to overwrite.");
WriteWarning($"The file '{fullPath}' already exists. Use -Force to overwrite.");
return;
}
var configService = new ConfigService(ApiClient, Logger);
// Use our safe execution method
var configContent = ExecuteAsyncTask(() => configService.ExportConfigAsync());
// Only continue if no exception occurred
if (ProcessingException != null || configContent == null)
{
return;
}
try
{
string fullPath = Path.FullName;
// Check if the file exists
if (File.Exists(fullPath) && !Force.IsPresent)
{
WriteError(new ErrorRecord(
new IOException($"The file '{fullPath}' already exists. Use -Force to overwrite."),
"FileExists",
ErrorCategory.ResourceExists,
fullPath));
return;
}
var configService = new ConfigService(ApiClient, Logger);
// Execute the async method synchronously on the main thread
var configContent = configService.ExportConfigAsync().GetAwaiter().GetResult();
// Create the directory if it doesn't exist
var directory = System.IO.Path.GetDirectoryName(fullPath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
@@ -71,7 +74,8 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (Exception ex)
{
HandleException(ex);
ProcessingException = ex;
WriteWarning($"Failed to write configuration to file: {ex.Message}");
}
}
}
@@ -52,54 +52,60 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var aliasService = new AliasService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var aliasService = new AliasService(ApiClient, Logger);
// Use our safe execution method
var result = ExecuteAsyncTask(() => aliasService.GetAliasAsync(Uuid));
if (ParameterSetName == "ByUuid")
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
var task = Task.Run(async () => await aliasService.GetAliasAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Alias);
return;
}
else
{
var task = Task.Run(async () => await aliasService.GetAliasesAsync());
var result = task.GetAwaiter().GetResult();
// Filter by name if specified
if (ParameterSetName == "ByName")
WriteObject(result.Alias);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => aliasService.GetAliasesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
// Filter by name if specified
if (ParameterSetName == "ByName")
{
var filteredByName = result.Rows.FindAll(a => a.Name.Equals(Name, StringComparison.OrdinalIgnoreCase));
// Further filter by type if specified
if (!string.IsNullOrEmpty(Type))
{
var filteredByName = result.Rows.FindAll(a => a.Name.Equals(Name, StringComparison.OrdinalIgnoreCase));
// Further filter by type if specified
if (!string.IsNullOrEmpty(Type))
{
var filteredByType = filteredByName.FindAll(a => a.Type.Equals(Type, StringComparison.OrdinalIgnoreCase));
WriteObject(filteredByType, true);
}
else
{
WriteObject(filteredByName, true);
}
}
// Filter by type if specified
else if (!string.IsNullOrEmpty(Type))
{
var filteredByType = result.Rows.FindAll(a => a.Type.Equals(Type, StringComparison.OrdinalIgnoreCase));
var filteredByType = filteredByName.FindAll(a => a.Type.Equals(Type, StringComparison.OrdinalIgnoreCase));
WriteObject(filteredByType, true);
}
else
{
WriteObject(result.Rows, true);
WriteObject(filteredByName, true);
}
}
}
catch (Exception ex)
{
HandleException(ex);
// Filter by type if specified
else if (!string.IsNullOrEmpty(Type))
{
var filteredByType = result.Rows.FindAll(a => a.Type.Equals(Type, StringComparison.OrdinalIgnoreCase));
WriteObject(filteredByType, true);
}
else
{
WriteObject(result.Rows, true);
}
}
}
}
@@ -21,21 +21,20 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var configService = new ConfigService(ApiClient, Logger);
var configService = new ConfigService(ApiClient, Logger);
var task = Task.Run(async () => await configService.GetConfigBackupsAsync());
var result = task.GetAwaiter().GetResult();
// Use our safe execution method
var result = ExecuteAsyncTask(() => configService.GetConfigBackupsAsync());
WriteObject(result.Backups, true);
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteObject(result.Backups, true);
}
}
}
@@ -28,17 +28,23 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
var configService = new ConfigService(ApiClient, Logger);
// Use our safe execution method
var configContent = ExecuteAsyncTask(() => configService.ExportConfigAsync());
// Only continue if no exception occurred
if (ProcessingException != null || configContent == null)
{
return;
}
// Convert the byte array to an XML document
var xmlDoc = new XmlDocument();
try
{
var configService = new ConfigService(ApiClient, Logger);
// Execute the async method synchronously on the main thread
var configContent = configService.ExportConfigAsync().GetAwaiter().GetResult();
// Convert the byte array to an XML document
var xmlDoc = new XmlDocument();
using (var memoryStream = new MemoryStream(configContent))
{
xmlDoc.Load(memoryStream);
@@ -49,7 +55,8 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (Exception ex)
{
HandleException(ex);
ProcessingException = ex;
WriteWarning($"Failed to parse XML configuration: {ex.Message}");
}
}
}
@@ -1,6 +1,8 @@
using System;
using System.Management.Automation;
using PSOPNSenseAPI.Logging;
using PSOPNSenseAPI.Models;
using PSOPNSenseAPI.Services;
namespace PSOPNSenseAPI.Cmdlets
{
@@ -15,35 +17,51 @@ namespace PSOPNSenseAPI.Cmdlets
/// </summary>
[Cmdlet(VerbsCommon.Get, "OPNSenseConnection")]
[OutputType(typeof(PSObject))]
public class GetOPNSenseConnectionCmdlet : PSCmdlet
public class GetOPNSenseConnectionCmdlet : OPNSenseBaseCmdlet
{
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var sessionState = OPNSenseSessionState.Instance;
// Create a logger
var logger = new PowerShellLogger(this);
if (sessionState.ApiClient == null)
{
if (!OPNSenseSession.IsConnected)
// Only write warning if WarningPreference is not SilentlyContinue
if (MyInvocation.BoundParameters.ContainsKey("WarningAction") ||
!ActionPreference.SilentlyContinue.Equals(SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.Continue)))
{
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));
// Return an empty connection info object
var emptyConnectionInfo = new PSObject();
emptyConnectionInfo.Properties.Add(new PSNoteProperty("Server", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("Connected", false));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("ApiVersion", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("ProductName", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("ProductVersion", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("OsVersion", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("Hostname", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("SystemTime", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("Uptime", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("CpuUsage", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("MemoryUsage", null));
emptyConnectionInfo.Properties.Add(new PSNoteProperty("LastUpdate", null));
WriteObject(connectionInfo);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(
ex,
"GetConnectionFailed",
ErrorCategory.ConnectionError,
null));
WriteObject(emptyConnectionInfo);
return;
}
// Create and return detailed connection info object
var connectionInfoHelper = new OPNSenseConnectionInfo(sessionState.ApiClient, logger);
var connectionInfo = connectionInfoHelper.GetConnectionInfo();
WriteObject(connectionInfo);
}
}
}
@@ -33,28 +33,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var cronService = new CronService(ApiClient, Logger);
var cronService = new CronService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await cronService.GetJobAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Job);
}
else
{
var task = Task.Run(async () => await cronService.GetJobsAsync());
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => cronService.GetJobAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Job);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => cronService.GetJobsAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -40,28 +40,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var dhcpService = new DHCPService(ApiClient, Logger);
var dhcpService = new DHCPService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await dhcpService.GetOptionAsync(Interface, Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Option);
}
else
{
var task = Task.Run(async () => await dhcpService.GetOptionsAsync(Interface));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => dhcpService.GetOptionAsync(Interface, Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Option);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => dhcpService.GetOptionsAsync(Interface));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -33,43 +33,49 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var dhcpService = new DHCPService(ApiClient, Logger);
if (ParameterSetName == "ByInterface")
{
var dhcpService = new DHCPService(ApiClient, Logger);
// Use our safe execution method
var result = ExecuteAsyncTask(() => dhcpService.GetServerAsync(Interface));
if (ParameterSetName == "ByInterface")
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
var task = Task.Run(async () => await dhcpService.GetServerAsync(Interface));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Server);
return;
}
else
{
var task = Task.Run(async () => await dhcpService.GetServersAsync());
var result = task.GetAwaiter().GetResult();
foreach (var kvp in result.Servers.Interfaces)
{
var server = new PSObject();
server.Properties.Add(new PSNoteProperty("Interface", kvp.Key));
server.Properties.Add(new PSNoteProperty("Enabled", kvp.Value.Enabled == "1"));
server.Properties.Add(new PSNoteProperty("RangeFrom", kvp.Value.RangeFrom));
server.Properties.Add(new PSNoteProperty("RangeTo", kvp.Value.RangeTo));
server.Properties.Add(new PSNoteProperty("DefaultLeaseTime", kvp.Value.DefaultLeaseTime));
server.Properties.Add(new PSNoteProperty("MaxLeaseTime", kvp.Value.MaxLeaseTime));
server.Properties.Add(new PSNoteProperty("Domain", kvp.Value.Domain));
server.Properties.Add(new PSNoteProperty("DnsServers", kvp.Value.DnsServers));
server.Properties.Add(new PSNoteProperty("Gateway", kvp.Value.Gateway));
WriteObject(server);
}
}
WriteObject(result.Server);
}
catch (Exception ex)
else
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => dhcpService.GetServersAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
foreach (var kvp in result.Servers.Interfaces)
{
var server = new PSObject();
server.Properties.Add(new PSNoteProperty("Interface", kvp.Key));
server.Properties.Add(new PSNoteProperty("Enabled", kvp.Value.Enabled == "1"));
server.Properties.Add(new PSNoteProperty("RangeFrom", kvp.Value.RangeFrom));
server.Properties.Add(new PSNoteProperty("RangeTo", kvp.Value.RangeTo));
server.Properties.Add(new PSNoteProperty("DefaultLeaseTime", kvp.Value.DefaultLeaseTime));
server.Properties.Add(new PSNoteProperty("MaxLeaseTime", kvp.Value.MaxLeaseTime));
server.Properties.Add(new PSNoteProperty("Domain", kvp.Value.Domain));
server.Properties.Add(new PSNoteProperty("DnsServers", kvp.Value.DnsServers));
server.Properties.Add(new PSNoteProperty("Gateway", kvp.Value.Gateway));
WriteObject(server);
}
}
}
}
@@ -40,28 +40,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var dhcpService = new DHCPService(ApiClient, Logger);
var dhcpService = new DHCPService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await dhcpService.GetStaticMappingAsync(Interface, Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Mapping);
}
else
{
var task = Task.Run(async () => await dhcpService.GetStaticMappingsAsync(Interface));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => dhcpService.GetStaticMappingAsync(Interface, Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Mapping);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => dhcpService.GetStaticMappingsAsync(Interface));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -21,21 +21,20 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var dnsService = new DNSService(ApiClient, Logger);
var dnsService = new DNSService(ApiClient, Logger);
var task = Task.Run(async () => await dnsService.GetDNSForwardingAsync());
var result = task.GetAwaiter().GetResult();
// Use our safe execution method
var result = ExecuteAsyncTask(() => dnsService.GetDNSForwardingAsync());
WriteObject(result.Forward);
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteObject(result.Forward);
}
}
}
@@ -33,28 +33,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var dnsService = new DNSService(ApiClient, Logger);
var dnsService = new DNSService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await dnsService.GetDNSForwardingHostAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Host);
}
else
{
var task = Task.Run(async () => await dnsService.GetDNSForwardingHostsAsync());
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => dnsService.GetDNSForwardingHostAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Host);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => dnsService.GetDNSForwardingHostsAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -33,28 +33,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var dnsService = new DNSService(ApiClient, Logger);
var dnsService = new DNSService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await dnsService.GetDNSOverrideAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Host);
}
else
{
var task = Task.Run(async () => await dnsService.GetDNSOverridesAsync());
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => dnsService.GetDNSOverrideAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Host);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => dnsService.GetDNSOverridesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -21,32 +21,31 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var dnsService = new DNSService(ApiClient, Logger);
// Use our safe execution method
var result = ExecuteAsyncTask(() => dnsService.GetDNSConfigAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
var dnsService = new DNSService(ApiClient, Logger);
var task = Task.Run(async () => await dnsService.GetDNSConfigAsync());
var result = task.GetAwaiter().GetResult();
var dnsConfig = new PSObject();
dnsConfig.Properties.Add(new PSNoteProperty("Enabled", result.Unbound.Enabled == "1"));
dnsConfig.Properties.Add(new PSNoteProperty("Port", result.Unbound.Port));
dnsConfig.Properties.Add(new PSNoteProperty("Interfaces", result.Unbound.Interfaces));
dnsConfig.Properties.Add(new PSNoteProperty("Forwarding", result.Unbound.Forwarding == "1"));
dnsConfig.Properties.Add(new PSNoteProperty("Forwarders", result.Unbound.Forwarders));
dnsConfig.Properties.Add(new PSNoteProperty("RegisterDhcp", result.Unbound.RegisterDhcp == "1"));
dnsConfig.Properties.Add(new PSNoteProperty("RegisterDhcpDomain", result.Unbound.RegisterDhcpDomain));
dnsConfig.Properties.Add(new PSNoteProperty("RegisterDhcpStatic", result.Unbound.RegisterDhcpStatic == "1"));
dnsConfig.Properties.Add(new PSNoteProperty("ActiveInterfaces", result.Unbound.ActiveInterfaces));
WriteObject(dnsConfig);
}
catch (Exception ex)
{
HandleException(ex);
return;
}
var dnsConfig = new PSObject();
dnsConfig.Properties.Add(new PSNoteProperty("Enabled", result.Unbound.Enabled == "1"));
dnsConfig.Properties.Add(new PSNoteProperty("Port", result.Unbound.Port));
dnsConfig.Properties.Add(new PSNoteProperty("Interfaces", result.Unbound.Interfaces));
dnsConfig.Properties.Add(new PSNoteProperty("Forwarding", result.Unbound.Forwarding == "1"));
dnsConfig.Properties.Add(new PSNoteProperty("Forwarders", result.Unbound.Forwarders));
dnsConfig.Properties.Add(new PSNoteProperty("RegisterDhcp", result.Unbound.RegisterDhcp == "1"));
dnsConfig.Properties.Add(new PSNoteProperty("RegisterDhcpDomain", result.Unbound.RegisterDhcpDomain));
dnsConfig.Properties.Add(new PSNoteProperty("RegisterDhcpStatic", result.Unbound.RegisterDhcpStatic == "1"));
dnsConfig.Properties.Add(new PSNoteProperty("ActiveInterfaces", result.Unbound.ActiveInterfaces));
WriteObject(dnsConfig);
}
}
}
@@ -58,28 +58,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var firewallService = new FirewallService(ApiClient, Logger);
var firewallService = new FirewallService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await firewallService.GetRuleAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rule);
}
else
{
var task = Task.Run(async () => await firewallService.GetRulesAsync(SearchPhrase, Page, RowCount));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => firewallService.GetRuleAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rule);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => firewallService.GetRulesAsync(SearchPhrase, Page, RowCount));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -55,57 +55,84 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var firmwareService = new FirmwareService(ApiClient, Logger);
switch (ParameterSetName)
{
var firmwareService = new FirmwareService(ApiClient, Logger);
case "Changelog":
// Use our safe execution method
var changelogResult = ExecuteAsyncTask(() => firmwareService.GetChangelogAsync());
switch (ParameterSetName)
{
case "Changelog":
var changelogTask = Task.Run(async () => await firmwareService.GetChangelogAsync());
var changelogResult = changelogTask.GetAwaiter().GetResult();
WriteObject(changelogResult.Changelog);
break;
// Only continue if no exception occurred
if (ProcessingException != null || changelogResult == null)
{
return;
}
case "Audit":
var auditTask = Task.Run(async () => await firmwareService.GetAuditAsync());
var auditResult = auditTask.GetAwaiter().GetResult();
WriteObject(auditResult.Audit, true);
break;
WriteObject(changelogResult.Changelog);
break;
case "Health":
var healthTask = Task.Run(async () => await firmwareService.GetHealthAsync());
var healthResult = healthTask.GetAwaiter().GetResult();
WriteObject(healthResult.Health);
break;
case "Audit":
// Use our safe execution method
var auditResult = ExecuteAsyncTask(() => firmwareService.GetAuditAsync());
case "Check":
var checkTask = Task.Run(async () => await firmwareService.CheckForUpdatesAsync());
var checkResult = checkTask.GetAwaiter().GetResult();
WriteObject($"Check for updates: {checkResult.Status}");
break;
// Only continue if no exception occurred
if (ProcessingException != null || auditResult == null)
{
return;
}
default:
var statusTask = Task.Run(async () => await firmwareService.GetStatusAsync());
var statusResult = statusTask.GetAwaiter().GetResult();
WriteObject(auditResult.Audit, true);
break;
var firmware = new PSObject();
firmware.Properties.Add(new PSNoteProperty("Status", statusResult.Status));
firmware.Properties.Add(new PSNoteProperty("Connection", statusResult.Connection));
firmware.Properties.Add(new PSNoteProperty("DownloadSize", statusResult.DownloadSize));
firmware.Properties.Add(new PSNoteProperty("LastCheck", statusResult.LastCheck));
firmware.Properties.Add(new PSNoteProperty("UpgradeMessage", statusResult.UpgradeMessage));
firmware.Properties.Add(new PSNoteProperty("Updates", statusResult.Updates));
WriteObject(firmware);
break;
}
}
catch (Exception ex)
{
HandleException(ex);
case "Health":
// Use our safe execution method
var healthResult = ExecuteAsyncTask(() => firmwareService.GetHealthAsync());
// Only continue if no exception occurred
if (ProcessingException != null || healthResult == null)
{
return;
}
WriteObject(healthResult.Health);
break;
case "Check":
// Use our safe execution method
var checkResult = ExecuteAsyncTask(() => firmwareService.CheckForUpdatesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || checkResult == null)
{
return;
}
WriteObject($"Check for updates: {checkResult.Status}");
break;
default:
// Use our safe execution method
var statusResult = ExecuteAsyncTask(() => firmwareService.GetStatusAsync());
// Only continue if no exception occurred
if (ProcessingException != null || statusResult == null)
{
return;
}
var firmware = new PSObject();
firmware.Properties.Add(new PSNoteProperty("Status", statusResult.Status));
firmware.Properties.Add(new PSNoteProperty("Connection", statusResult.Connection));
firmware.Properties.Add(new PSNoteProperty("DownloadSize", statusResult.DownloadSize));
firmware.Properties.Add(new PSNoteProperty("LastCheck", statusResult.LastCheck));
firmware.Properties.Add(new PSNoteProperty("UpgradeMessage", statusResult.UpgradeMessage));
firmware.Properties.Add(new PSNoteProperty("Updates", statusResult.Updates));
WriteObject(firmware);
break;
}
}
}
@@ -39,69 +39,81 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var gatewayService = new GatewayService(ApiClient, Logger);
var gatewayService = new GatewayService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
if (ParameterSetName == "ByUuid")
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => gatewayService.GetGatewayAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
var task = Task.Run(async () => await gatewayService.GetGatewayAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Gateway);
return;
}
WriteObject(result.Gateway);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => gatewayService.GetGatewaysAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
if (IncludeStatus.IsPresent)
{
// Use our safe execution method
var statusResult = ExecuteAsyncTask(() => gatewayService.GetGatewayStatusAsync());
// Only continue if no exception occurred
if (ProcessingException != null || statusResult == null)
{
return;
}
foreach (var gateway in result.Rows)
{
var gatewayWithStatus = new PSObject();
gatewayWithStatus.Properties.Add(new PSNoteProperty("Uuid", gateway.Uuid));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Name", gateway.Name));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Interface", gateway.Interface));
gatewayWithStatus.Properties.Add(new PSNoteProperty("IpAddress", gateway.IpAddress));
gatewayWithStatus.Properties.Add(new PSNoteProperty("MonitorIp", gateway.MonitorIp));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Description", gateway.Description));
gatewayWithStatus.Properties.Add(new PSNoteProperty("IsDefault", gateway.IsDefault == "1"));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Disabled", gateway.Disabled == "1"));
if (statusResult.Items.ContainsKey(gateway.Name))
{
var status = statusResult.Items[gateway.Name];
gatewayWithStatus.Properties.Add(new PSNoteProperty("Status", status.Status));
gatewayWithStatus.Properties.Add(new PSNoteProperty("RTT", status.RTT));
gatewayWithStatus.Properties.Add(new PSNoteProperty("StdDev", status.StdDev));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Loss", status.Loss));
}
else
{
gatewayWithStatus.Properties.Add(new PSNoteProperty("Status", "Unknown"));
gatewayWithStatus.Properties.Add(new PSNoteProperty("RTT", "N/A"));
gatewayWithStatus.Properties.Add(new PSNoteProperty("StdDev", "N/A"));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Loss", "N/A"));
}
WriteObject(gatewayWithStatus);
}
}
else
{
var task = Task.Run(async () => await gatewayService.GetGatewaysAsync());
var result = task.GetAwaiter().GetResult();
if (IncludeStatus.IsPresent)
{
var statusTask = Task.Run(async () => await gatewayService.GetGatewayStatusAsync());
var statusResult = statusTask.GetAwaiter().GetResult();
foreach (var gateway in result.Rows)
{
var gatewayWithStatus = new PSObject();
gatewayWithStatus.Properties.Add(new PSNoteProperty("Uuid", gateway.Uuid));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Name", gateway.Name));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Interface", gateway.Interface));
gatewayWithStatus.Properties.Add(new PSNoteProperty("IpAddress", gateway.IpAddress));
gatewayWithStatus.Properties.Add(new PSNoteProperty("MonitorIp", gateway.MonitorIp));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Description", gateway.Description));
gatewayWithStatus.Properties.Add(new PSNoteProperty("IsDefault", gateway.IsDefault == "1"));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Disabled", gateway.Disabled == "1"));
if (statusResult.Items.ContainsKey(gateway.Name))
{
var status = statusResult.Items[gateway.Name];
gatewayWithStatus.Properties.Add(new PSNoteProperty("Status", status.Status));
gatewayWithStatus.Properties.Add(new PSNoteProperty("RTT", status.RTT));
gatewayWithStatus.Properties.Add(new PSNoteProperty("StdDev", status.StdDev));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Loss", status.Loss));
}
else
{
gatewayWithStatus.Properties.Add(new PSNoteProperty("Status", "Unknown"));
gatewayWithStatus.Properties.Add(new PSNoteProperty("RTT", "N/A"));
gatewayWithStatus.Properties.Add(new PSNoteProperty("StdDev", "N/A"));
gatewayWithStatus.Properties.Add(new PSNoteProperty("Loss", "N/A"));
}
WriteObject(gatewayWithStatus);
}
}
else
{
WriteObject(result.Rows, true);
}
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
{
HandleException(ex);
}
}
}
}
@@ -33,28 +33,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var interfaceService = new InterfaceService(ApiClient, Logger);
var interfaceService = new InterfaceService(ApiClient, Logger);
if (ParameterSetName == "ByName")
{
var task = Task.Run(async () => await interfaceService.GetInterfaceDetailAsync(Name));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Interface);
}
else
{
var task = Task.Run(async () => await interfaceService.GetInterfacesAsync());
var result = task.GetAwaiter().GetResult();
WriteObject(result.Interfaces, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByName")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => interfaceService.GetInterfaceDetailAsync(Name));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Interface);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => interfaceService.GetInterfacesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Interfaces, true);
}
}
}
@@ -26,33 +26,32 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var interfaceService = new InterfaceService(ApiClient, Logger);
// Use our safe execution method
var result = ExecuteAsyncTask(() => interfaceService.GetInterfaceStatisticsAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
var interfaceService = new InterfaceService(ApiClient, Logger);
var task = Task.Run(async () => await interfaceService.GetInterfaceStatisticsAsync());
var result = task.GetAwaiter().GetResult();
foreach (var kvp in result.Statistics)
{
var stats = new PSObject();
stats.Properties.Add(new PSNoteProperty("Name", kvp.Key));
stats.Properties.Add(new PSNoteProperty("InPackets", kvp.Value.InPackets));
stats.Properties.Add(new PSNoteProperty("InBytes", kvp.Value.InBytes));
stats.Properties.Add(new PSNoteProperty("InErrors", 0));
stats.Properties.Add(new PSNoteProperty("OutPackets", kvp.Value.OutPackets));
stats.Properties.Add(new PSNoteProperty("OutBytes", kvp.Value.OutBytes));
stats.Properties.Add(new PSNoteProperty("OutErrors", 0));
stats.Properties.Add(new PSNoteProperty("Collisions", 0));
WriteObject(stats);
}
return;
}
catch (Exception ex)
foreach (var kvp in result.Statistics)
{
HandleException(ex);
var stats = new PSObject();
stats.Properties.Add(new PSNoteProperty("Name", kvp.Key));
stats.Properties.Add(new PSNoteProperty("InPackets", kvp.Value.InPackets));
stats.Properties.Add(new PSNoteProperty("InBytes", kvp.Value.InBytes));
stats.Properties.Add(new PSNoteProperty("InErrors", 0));
stats.Properties.Add(new PSNoteProperty("OutPackets", kvp.Value.OutPackets));
stats.Properties.Add(new PSNoteProperty("OutBytes", kvp.Value.OutBytes));
stats.Properties.Add(new PSNoteProperty("OutErrors", 0));
stats.Properties.Add(new PSNoteProperty("Collisions", 0));
WriteObject(stats);
}
}
}
@@ -43,60 +43,59 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var pluginService = new PluginService(ApiClient, Logger);
// Use our safe execution method
var result = ExecuteAsyncTask(() => pluginService.GetPluginsAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
var pluginService = new PluginService(ApiClient, Logger);
return;
}
var task = Task.Run(async () => await pluginService.GetPluginsAsync());
var result = task.GetAwaiter().GetResult();
if (ParameterSetName == "Installed" || ParameterSetName == "")
if (ParameterSetName == "Installed" || ParameterSetName == "")
{
foreach (var kvp in result.Installed)
{
foreach (var kvp in result.Installed)
{
var plugin = new PSObject();
plugin.Properties.Add(new PSNoteProperty("Name", kvp.Key));
plugin.Properties.Add(new PSNoteProperty("Version", kvp.Value.Version));
plugin.Properties.Add(new PSNoteProperty("Comment", kvp.Value.Comment));
plugin.Properties.Add(new PSNoteProperty("Repository", kvp.Value.Repository));
plugin.Properties.Add(new PSNoteProperty("Origin", kvp.Value.Origin));
plugin.Properties.Add(new PSNoteProperty("License", kvp.Value.License));
plugin.Properties.Add(new PSNoteProperty("FlatSize", kvp.Value.FlatSize));
plugin.Properties.Add(new PSNoteProperty("Locked", kvp.Value.Locked == "1"));
plugin.Properties.Add(new PSNoteProperty("Enabled", kvp.Value.Enabled == "1"));
plugin.Properties.Add(new PSNoteProperty("Status", "Installed"));
WriteObject(plugin);
}
}
var plugin = new PSObject();
plugin.Properties.Add(new PSNoteProperty("Name", kvp.Key));
plugin.Properties.Add(new PSNoteProperty("Version", kvp.Value.Version));
plugin.Properties.Add(new PSNoteProperty("Comment", kvp.Value.Comment));
plugin.Properties.Add(new PSNoteProperty("Repository", kvp.Value.Repository));
plugin.Properties.Add(new PSNoteProperty("Origin", kvp.Value.Origin));
plugin.Properties.Add(new PSNoteProperty("License", kvp.Value.License));
plugin.Properties.Add(new PSNoteProperty("FlatSize", kvp.Value.FlatSize));
plugin.Properties.Add(new PSNoteProperty("Locked", kvp.Value.Locked == "1"));
plugin.Properties.Add(new PSNoteProperty("Enabled", kvp.Value.Enabled == "1"));
plugin.Properties.Add(new PSNoteProperty("Status", "Installed"));
if (ParameterSetName == "Available" || ParameterSetName == "")
{
foreach (var kvp in result.Available)
{
// Skip if the plugin is already installed
if (result.Installed.ContainsKey(kvp.Key))
continue;
var plugin = new PSObject();
plugin.Properties.Add(new PSNoteProperty("Name", kvp.Key));
plugin.Properties.Add(new PSNoteProperty("Version", kvp.Value.Version));
plugin.Properties.Add(new PSNoteProperty("Comment", kvp.Value.Comment));
plugin.Properties.Add(new PSNoteProperty("Repository", kvp.Value.Repository));
plugin.Properties.Add(new PSNoteProperty("Origin", kvp.Value.Origin));
plugin.Properties.Add(new PSNoteProperty("License", kvp.Value.License));
plugin.Properties.Add(new PSNoteProperty("FlatSize", kvp.Value.FlatSize));
plugin.Properties.Add(new PSNoteProperty("Status", "Available"));
WriteObject(plugin);
}
WriteObject(plugin);
}
}
catch (Exception ex)
if (ParameterSetName == "Available" || ParameterSetName == "")
{
HandleException(ex);
foreach (var kvp in result.Available)
{
// Skip if the plugin is already installed
if (result.Installed.ContainsKey(kvp.Key))
continue;
var plugin = new PSObject();
plugin.Properties.Add(new PSNoteProperty("Name", kvp.Key));
plugin.Properties.Add(new PSNoteProperty("Version", kvp.Value.Version));
plugin.Properties.Add(new PSNoteProperty("Comment", kvp.Value.Comment));
plugin.Properties.Add(new PSNoteProperty("Repository", kvp.Value.Repository));
plugin.Properties.Add(new PSNoteProperty("Origin", kvp.Value.Origin));
plugin.Properties.Add(new PSNoteProperty("License", kvp.Value.License));
plugin.Properties.Add(new PSNoteProperty("FlatSize", kvp.Value.FlatSize));
plugin.Properties.Add(new PSNoteProperty("Status", "Available"));
WriteObject(plugin);
}
}
}
}
@@ -19,7 +19,7 @@ namespace PSOPNSenseAPI.Cmdlets
/// </summary>
[Cmdlet(VerbsCommon.Get, "OPNSensePortForwardingRule")]
[OutputType(typeof(PortForwardingRule))]
public class GetOPNSensePortForwardingRuleCmdlet : OPNSenseCmdlet
public class GetOPNSensePortForwardingRuleCmdlet : OPNSenseBaseCmdlet
{
/// <summary>
/// <para type="description">The UUID of the port forwarding rule to get. If not specified, all rules are returned.</para>
@@ -30,14 +30,21 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
var portForwardingService = new PortForwardingService(SessionState.ApiClient);
var portForwardingService = new PortForwardingService(ApiClient);
if (!string.IsNullOrEmpty(Uuid))
{
// Get a specific rule
var rule = Task.Run(async () => await portForwardingService.GetPortForwardingRuleAsync(Uuid)).GetAwaiter().GetResult();
// Use our safe execution method
var rule = ExecuteAsyncTask(() => portForwardingService.GetPortForwardingRuleAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null)
{
return;
}
if (rule != null)
{
WriteObject(rule);
@@ -49,8 +56,15 @@ namespace PSOPNSenseAPI.Cmdlets
}
else
{
// Get all rules
var rules = Task.Run(async () => await portForwardingService.GetPortForwardingRulesAsync()).GetAwaiter().GetResult();
// Use our safe execution method
var rules = ExecuteAsyncTask(() => portForwardingService.GetPortForwardingRulesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || rules == null)
{
return;
}
WriteObject(rules, true);
}
}
@@ -33,28 +33,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var routeService = new RouteService(ApiClient, Logger);
var routeService = new RouteService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await routeService.GetRouteAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Route);
}
else
{
var task = Task.Run(async () => await routeService.GetRoutesAsync());
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => routeService.GetRouteAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Route);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => routeService.GetRoutesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -21,21 +21,20 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var systemDNSService = new SystemDNSService(ApiClient, Logger);
var systemDNSService = new SystemDNSService(ApiClient, Logger);
var task = Task.Run(async () => await systemDNSService.GetSystemDNSAsync());
var result = task.GetAwaiter().GetResult();
// Use our safe execution method
var result = ExecuteAsyncTask(() => systemDNSService.GetSystemDNSAsync());
WriteObject(result.System);
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteObject(result.System);
}
}
}
@@ -33,64 +33,77 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </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
var isInstalledTask = Task.Run(async () => await tailscaleService.IsPluginInstalledAsync());
var isInstalled = isInstalledTask.GetAwaiter().GetResult();
if (!isInstalled)
{
WriteWarning("Tailscale plugin is not installed on the OPNSense firewall.");
if (!isInstalled)
var statusResult = new PSObject();
statusResult.Properties.Add(new PSNoteProperty("PluginInstalled", false));
statusResult.Properties.Add(new PSNoteProperty("Running", false));
statusResult.Properties.Add(new PSNoteProperty("Enabled", false));
statusResult.Properties.Add(new PSNoteProperty("Status", "Plugin not installed"));
WriteObject(statusResult);
return;
}
// Get the status
var status = ExecuteAsyncTask(() => tailscaleService.GetStatusAsync());
// Only continue if no exception occurred
if (ProcessingException != null || status == null)
{
return;
}
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("PluginInstalled", true));
result.Properties.Add(new PSNoteProperty("Running", status.Running));
result.Properties.Add(new PSNoteProperty("Enabled", status.Enabled));
result.Properties.Add(new PSNoteProperty("Status", status.Status));
// Get interfaces if requested
if (IncludeInterfaces.IsPresent)
{
var interfaces = ExecuteAsyncTask(() => tailscaleService.GetInterfacesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || interfaces == null)
{
WriteWarning("Tailscale plugin is not installed on the OPNSense firewall.");
var statusResult = new PSObject();
statusResult.Properties.Add(new PSNoteProperty("PluginInstalled", false));
statusResult.Properties.Add(new PSNoteProperty("Running", false));
statusResult.Properties.Add(new PSNoteProperty("Enabled", false));
statusResult.Properties.Add(new PSNoteProperty("Status", "Plugin not installed"));
WriteObject(statusResult);
return;
}
// Get the status
var statusTask = Task.Run(async () => await tailscaleService.GetStatusAsync());
var status = statusTask.GetAwaiter().GetResult();
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("PluginInstalled", true));
result.Properties.Add(new PSNoteProperty("Running", status.Running));
result.Properties.Add(new PSNoteProperty("Enabled", status.Enabled));
result.Properties.Add(new PSNoteProperty("Status", status.Status));
// Get interfaces if requested
if (IncludeInterfaces.IsPresent)
{
var interfacesTask = Task.Run(async () => await tailscaleService.GetInterfacesAsync());
var interfaces = interfacesTask.GetAwaiter().GetResult();
result.Properties.Add(new PSNoteProperty("Interfaces", interfaces.Interfaces));
}
// Get settings if requested
if (IncludeSettings.IsPresent)
{
var settingsTask = Task.Run(async () => await tailscaleService.GetSettingsAsync());
var settings = settingsTask.GetAwaiter().GetResult();
result.Properties.Add(new PSNoteProperty("Settings", settings.General));
}
WriteObject(result);
result.Properties.Add(new PSNoteProperty("Interfaces", interfaces.Interfaces));
}
catch (Exception ex)
// Get settings if requested
if (IncludeSettings.IsPresent)
{
HandleException(ex);
var settings = ExecuteAsyncTask(() => tailscaleService.GetSettingsAsync());
// Only continue if no exception occurred
if (ProcessingException != null || settings == null)
{
return;
}
result.Properties.Add(new PSNoteProperty("Settings", settings.General));
}
WriteObject(result);
}
}
}
@@ -33,28 +33,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var userService = new UserService(ApiClient, Logger);
var userService = new UserService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await userService.GetUserAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.User);
}
else
{
var task = Task.Run(async () => await userService.GetUsersAsync());
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => userService.GetUserAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.User);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => userService.GetUsersAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -33,28 +33,35 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
{
var interfaceService = new InterfaceService(ApiClient, Logger);
var interfaceService = new InterfaceService(ApiClient, Logger);
if (ParameterSetName == "ByUuid")
{
var task = Task.Run(async () => await interfaceService.GetVLANAsync(Uuid));
var result = task.GetAwaiter().GetResult();
WriteObject(result.Vlan);
}
else
{
var task = Task.Run(async () => await interfaceService.GetVLANsAsync());
var result = task.GetAwaiter().GetResult();
WriteObject(result.Rows, true);
}
}
catch (Exception ex)
if (ParameterSetName == "ByUuid")
{
HandleException(ex);
// Use our safe execution method
var result = ExecuteAsyncTask(() => interfaceService.GetVLANAsync(Uuid));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Vlan);
}
else
{
// Use our safe execution method
var result = ExecuteAsyncTask(() => interfaceService.GetVLANsAsync());
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteObject(result.Rows, true);
}
}
}
@@ -35,42 +35,46 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
string fullPath = Path.FullName;
// Check if the file exists
if (!File.Exists(fullPath))
{
ProcessingException = new FileNotFoundException($"The file '{fullPath}' does not exist.");
WriteWarning($"The file '{fullPath}' does not exist.");
return;
}
if (!Force.IsPresent && !ShouldProcess(fullPath, "Import configuration"))
{
return;
}
var configService = new ConfigService(ApiClient, Logger);
try
{
string fullPath = Path.FullName;
// Check if the file exists
if (!File.Exists(fullPath))
{
WriteError(new ErrorRecord(
new FileNotFoundException($"The file '{fullPath}' does not exist."),
"FileNotFound",
ErrorCategory.ObjectNotFound,
fullPath));
return;
}
if (!Force.IsPresent && !ShouldProcess(fullPath, "Import configuration"))
{
return;
}
var configService = new ConfigService(ApiClient, Logger);
// Read the configuration file
var configContent = File.ReadAllBytes(fullPath);
// Execute the async method synchronously on the main thread
var result = configService.ImportConfigAsync(configContent).GetAwaiter().GetResult();
// Use our safe execution method
var result = ExecuteAsyncTask(() => configService.ImportConfigAsync(configContent));
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
return;
}
WriteVerbose($"Imported configuration from {fullPath}: {result.Status}");
WriteWarning("The firewall is restarting. You may need to reconnect after it comes back online.");
}
catch (Exception ex)
{
HandleException(ex);
ProcessingException = ex;
WriteWarning($"Failed to read or process configuration file: {ex.Message}");
}
}
}
@@ -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
/// </summary>
[Cmdlet(VerbsLifecycle.Invoke, "OPNSenseNetworkCalculation")]
[OutputType(typeof(PSObject), typeof(bool), typeof(IPNetwork2[]))]
public class InvokeOPNSenseNetworkCalculationCmdlet : PSCmdlet
public class InvokeOPNSenseNetworkCalculationCmdlet : OPNSenseBaseCmdlet
{
/// <summary>
/// <para type="description">The network in CIDR notation to perform calculations on.</para>
@@ -72,111 +73,93 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </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
{
// 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;
}
}
@@ -202,11 +185,7 @@ namespace PSOPNSenseAPI.Cmdlets
{
if (prefixLength <= network.Cidr)
{
WriteError(new ErrorRecord(
new ArgumentException($"Prefix length ({prefixLength}) must be greater than the network CIDR ({network.Cidr})."),
"InvalidPrefixLength",
ErrorCategory.InvalidArgument,
null));
ProcessingException = new ArgumentException($"Prefix length ({prefixLength}) must be greater than the network CIDR ({network.Cidr}).");
return;
}
@@ -223,11 +202,7 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (ArgumentException ex)
{
WriteError(new ErrorRecord(
ex,
"InvalidSubnetCount",
ErrorCategory.InvalidArgument,
null));
ProcessingException = ex;
}
}
@@ -253,11 +228,7 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (Exception ex)
{
WriteError(new ErrorRecord(
ex,
"InvalidIPAddress",
ErrorCategory.InvalidArgument,
null));
ProcessingException = ex;
}
}
@@ -284,11 +255,7 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (Exception ex)
{
WriteError(new ErrorRecord(
ex,
"InvalidAdditionalNetwork",
ErrorCategory.InvalidArgument,
null));
ProcessingException = ex;
}
}
@@ -309,11 +276,7 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (Exception ex)
{
WriteError(new ErrorRecord(
ex,
"SupernetError",
ErrorCategory.InvalidOperation,
null));
ProcessingException = ex;
}
}
@@ -334,11 +297,7 @@ namespace PSOPNSenseAPI.Cmdlets
}
catch (Exception ex)
{
WriteError(new ErrorRecord(
ex,
"SupernetSummarizeError",
ErrorCategory.InvalidOperation,
null));
ProcessingException = ex;
}
}
}
@@ -95,54 +95,59 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
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);
}
}
}
@@ -82,44 +82,49 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </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,
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);
}
}
}
@@ -61,40 +61,45 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
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);
}
}
}
@@ -66,41 +66,46 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
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);
}
}
}
@@ -53,40 +53,45 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var dnsService = new DNSService(ApiClient, Logger);
var host = new DNSForwardingHostConfig
{
var dnsService = new DNSService(ApiClient, Logger);
Domain = Domain,
Server = Server,
Description = Description,
Enabled = Enabled.IsPresent ? "1" : "0"
};
var host = new DNSForwardingHostConfig
// Use our safe execution method
var createResult = ExecuteAsyncTask(() => dnsService.CreateDNSForwardingHostAsync(host));
// Only continue if no exception occurred
if (ProcessingException != null || createResult == null)
{
return;
}
WriteVerbose($"Created DNS forwarding host with UUID {createResult.Uuid}");
// Apply changes if requested
if (Apply.IsPresent)
{
// Use our safe execution method
var applyResult = ExecuteAsyncTask(() => dnsService.ApplyDNSChangesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || applyResult == null)
{
Domain = Domain,
Server = Server,
Description = Description,
Enabled = Enabled.IsPresent ? "1" : "0"
};
var createTask = Task.Run(async () => await dnsService.CreateDNSForwardingHostAsync(host));
var createResult = createTask.GetAwaiter().GetResult();
WriteVerbose($"Created DNS forwarding host with UUID {createResult.Uuid}");
// Apply changes if requested
if (Apply.IsPresent)
{
var applyTask = Task.Run(async () => await dnsService.ApplyDNSChangesAsync());
var applyResult = applyTask.GetAwaiter().GetResult();
WriteVerbose($"DNS changes applied: {applyResult.Status}");
return;
}
WriteObject(createResult.Uuid);
}
catch (Exception ex)
{
HandleException(ex);
WriteVerbose($"DNS changes applied: {applyResult.Status}");
}
WriteObject(createResult.Uuid);
}
}
}
@@ -60,41 +60,46 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var dnsService = new DNSService(ApiClient, Logger);
var dnsOverride = new DNSOverrideConfig
{
var dnsService = new DNSService(ApiClient, Logger);
Hostname = Hostname,
Domain = Domain,
IpAddress = IpAddress,
Description = Description,
Enabled = Enabled.IsPresent ? "1" : "0"
};
var dnsOverride = new DNSOverrideConfig
// Use our safe execution method
var createResult = ExecuteAsyncTask(() => dnsService.CreateDNSOverrideAsync(dnsOverride));
// Only continue if no exception occurred
if (ProcessingException != null || createResult == null)
{
return;
}
WriteVerbose($"Created DNS override with UUID {createResult.Uuid}");
// Apply the changes if requested
if (Apply.IsPresent)
{
// Use our safe execution method
var applyResult = ExecuteAsyncTask(() => dnsService.ApplyDNSChangesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || applyResult == null)
{
Hostname = Hostname,
Domain = Domain,
IpAddress = IpAddress,
Description = Description,
Enabled = Enabled.IsPresent ? "1" : "0"
};
var createTask = Task.Run(async () => await dnsService.CreateDNSOverrideAsync(dnsOverride));
var createResult = createTask.GetAwaiter().GetResult();
WriteVerbose($"Created DNS override with UUID {createResult.Uuid}");
// Apply the changes if requested
if (Apply.IsPresent)
{
var applyTask = Task.Run(async () => await dnsService.ApplyDNSChangesAsync());
var applyResult = applyTask.GetAwaiter().GetResult();
WriteVerbose($"DNS changes applied: {applyResult.Status}");
return;
}
WriteObject(createResult.Uuid);
}
catch (Exception ex)
{
HandleException(ex);
WriteVerbose($"DNS changes applied: {applyResult.Status}");
}
WriteObject(createResult.Uuid);
}
}
}
@@ -83,35 +83,34 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var firewallService = new FirewallService(ApiClient, Logger);
var rule = new FirewallRuleCreate
{
var firewallService = new FirewallService(ApiClient, Logger);
Description = Description,
Protocol = Protocol,
SourceNet = SourceNet,
SourcePort = SourcePort,
DestinationNet = DestinationNet,
DestinationPort = DestinationPort,
Action = Action,
Sequence = Sequence,
Enabled = Enabled.IsPresent ? "1" : "0"
};
var rule = new FirewallRuleCreate
{
Description = Description,
Protocol = Protocol,
SourceNet = SourceNet,
SourcePort = SourcePort,
DestinationNet = DestinationNet,
DestinationPort = DestinationPort,
Action = Action,
Sequence = Sequence,
Enabled = Enabled.IsPresent ? "1" : "0"
};
// Use our safe execution method
var result = ExecuteAsyncTask(() => firewallService.CreateRuleAsync(rule));
var task = Task.Run(async () => await firewallService.CreateRuleAsync(rule));
var result = task.GetAwaiter().GetResult();
WriteVerbose($"Created firewall rule with UUID {result.Uuid}");
WriteObject(result.Uuid);
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteVerbose($"Created firewall rule with UUID {result.Uuid}");
WriteObject(result.Uuid);
}
}
}
@@ -103,47 +103,52 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var gatewayService = new GatewayService(ApiClient, Logger);
var gateway = new GatewayConfig
{
var gatewayService = new GatewayService(ApiClient, Logger);
Name = Name,
Interface = Interface,
IpAddress = IpAddress,
MonitorIp = MonitorIp ?? IpAddress,
Description = Description,
IsDefault = Default.IsPresent ? "1" : "0",
Disabled = Disabled.IsPresent ? "1" : "0",
Weight = Weight?.ToString() ?? "1",
IpProtocol = IpProtocol,
MonitorDisable = DisableMonitoring.IsPresent ? "1" : "0",
ForceDown = ForceDown.IsPresent ? "1" : "0"
};
var gateway = new GatewayConfig
// Use our safe execution method
var createResult = ExecuteAsyncTask(() => gatewayService.CreateGatewayAsync(gateway));
// Only continue if no exception occurred
if (ProcessingException != null || createResult == null)
{
return;
}
WriteVerbose($"Created gateway with UUID {createResult.Uuid}");
// Apply changes if requested
if (Apply.IsPresent)
{
// Use our safe execution method
var applyResult = ExecuteAsyncTask(() => gatewayService.ApplyGatewayChangesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || applyResult == null)
{
Name = Name,
Interface = Interface,
IpAddress = IpAddress,
MonitorIp = MonitorIp ?? IpAddress,
Description = Description,
IsDefault = Default.IsPresent ? "1" : "0",
Disabled = Disabled.IsPresent ? "1" : "0",
Weight = Weight?.ToString() ?? "1",
IpProtocol = IpProtocol,
MonitorDisable = DisableMonitoring.IsPresent ? "1" : "0",
ForceDown = ForceDown.IsPresent ? "1" : "0"
};
var createTask = Task.Run(async () => await gatewayService.CreateGatewayAsync(gateway));
var createResult = createTask.GetAwaiter().GetResult();
WriteVerbose($"Created gateway with UUID {createResult.Uuid}");
// Apply changes if requested
if (Apply.IsPresent)
{
var applyTask = Task.Run(async () => await gatewayService.ApplyGatewayChangesAsync());
var applyResult = applyTask.GetAwaiter().GetResult();
WriteVerbose($"Gateway changes applied: {applyResult.Status}");
return;
}
WriteObject(createResult.Uuid);
}
catch (Exception ex)
{
HandleException(ex);
WriteVerbose($"Gateway changes applied: {applyResult.Status}");
}
WriteObject(createResult.Uuid);
}
}
}
@@ -15,7 +15,7 @@ namespace PSOPNSenseAPI.Cmdlets
/// </summary>
[Cmdlet(VerbsCommon.New, "OPNSensePortForwardingRule", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium)]
[OutputType(typeof(PortForwardingRule))]
public class NewOPNSensePortForwardingRuleCmdlet : OPNSenseCmdlet
public class NewOPNSensePortForwardingRuleCmdlet : OPNSenseBaseCmdlet
{
/// <summary>
/// <para type="description">Whether the rule is enabled.</para>
@@ -106,7 +106,7 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
var rule = new PortForwardingRule
{
@@ -127,8 +127,16 @@ namespace PSOPNSenseAPI.Cmdlets
if (Force || ShouldProcess($"OPNSense firewall", $"Create port forwarding rule from {Destination}:{DestinationPort} to {TargetIP}:{TargetPort}"))
{
var portForwardingService = new PortForwardingService(SessionState.ApiClient);
var uuid = Task.Run(async () => await portForwardingService.CreatePortForwardingRuleAsync(rule)).GetAwaiter().GetResult();
var portForwardingService = new PortForwardingService(ApiClient);
// Use our safe execution method
var uuid = ExecuteAsyncTask(() => portForwardingService.CreatePortForwardingRuleAsync(rule));
// Only continue if no exception occurred
if (ProcessingException != null)
{
return;
}
if (!string.IsNullOrEmpty(uuid))
{
@@ -138,11 +146,8 @@ namespace PSOPNSenseAPI.Cmdlets
}
else
{
WriteError(new ErrorRecord(
new PSInvalidOperationException("Failed to create port forwarding rule."),
"PortForwardingRuleCreationFailed",
ErrorCategory.InvalidOperation,
null));
ProcessingException = new PSInvalidOperationException("Failed to create port forwarding rule.");
WriteWarning("Failed to create port forwarding rule.");
}
}
}
@@ -53,40 +53,45 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var routeService = new RouteService(ApiClient, Logger);
var route = new RouteConfig
{
var routeService = new RouteService(ApiClient, Logger);
Network = Network,
Gateway = Gateway,
Description = Description,
Disabled = Disabled.IsPresent ? "1" : "0"
};
var route = new RouteConfig
// Use our safe execution method
var createResult = ExecuteAsyncTask(() => routeService.CreateRouteAsync(route));
// Only continue if no exception occurred
if (ProcessingException != null || createResult == null)
{
return;
}
WriteVerbose($"Created route with UUID {createResult.Uuid}");
// Apply changes if requested
if (Apply.IsPresent)
{
// Use our safe execution method
var applyResult = ExecuteAsyncTask(() => routeService.ApplyRouteChangesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || applyResult == null)
{
Network = Network,
Gateway = Gateway,
Description = Description,
Disabled = Disabled.IsPresent ? "1" : "0"
};
var createTask = Task.Run(async () => await routeService.CreateRouteAsync(route));
var createResult = createTask.GetAwaiter().GetResult();
WriteVerbose($"Created route with UUID {createResult.Uuid}");
// Apply changes if requested
if (Apply.IsPresent)
{
var applyTask = Task.Run(async () => await routeService.ApplyRouteChangesAsync());
var applyResult = applyTask.GetAwaiter().GetResult();
WriteVerbose($"Route changes applied: {applyResult.Status}");
return;
}
WriteObject(createResult.Uuid);
}
catch (Exception ex)
{
HandleException(ex);
WriteVerbose($"Route changes applied: {applyResult.Status}");
}
WriteObject(createResult.Uuid);
}
}
}
@@ -99,199 +99,219 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
// Parse the network CIDR
var ipNetwork = IPNetwork2.Parse(Network);
// Validate subnet mask bits
if (SubnetMaskBits <= ipNetwork.Cidr)
{
// Parse the network CIDR
var ipNetwork = IPNetwork2.Parse(Network);
ProcessingException = new ArgumentException($"Subnet mask bits ({SubnetMaskBits}) must be greater than the network CIDR ({ipNetwork.Cidr}).");
WriteWarning($"Subnet mask bits ({SubnetMaskBits}) must be greater than the network CIDR ({ipNetwork.Cidr}).");
return;
}
// Validate subnet mask bits
if (SubnetMaskBits <= ipNetwork.Cidr)
// Calculate the number of subnets
int numSubnets = (int)Math.Pow(2, SubnetMaskBits - ipNetwork.Cidr);
// Calculate the maximum VLAN ID
int maxVlanId = StartingVlanId + (numSubnets - 1) * VlanIdIncrement;
// Validate the maximum VLAN ID
if (maxVlanId > 4094)
{
ProcessingException = new ArgumentException($"The maximum VLAN ID ({maxVlanId}) exceeds the maximum allowed value (4094).");
WriteWarning($"The maximum VLAN ID ({maxVlanId}) exceeds the maximum allowed value (4094).");
return;
}
// Get confirmation
if (!Force.IsPresent && !ShouldProcess($"Create {numSubnets} VLANs for subnets of {Network} with VLAN IDs {StartingVlanId}-{maxVlanId}", "New-OPNSenseSubnetVLANs"))
{
return;
}
// Get the interface service
var interfaceService = new InterfaceService(ApiClient, Logger);
// Verify the parent interface exists
var interfaceDetail = ExecuteAsyncTask(() => interfaceService.GetInterfaceDetailAsync(ParentInterface));
// Only continue if no exception occurred
if (ProcessingException != null)
{
return;
}
// Get existing VLANs
var vlansResult = ExecuteAsyncTask(() => interfaceService.GetVLANsAsync());
// Only continue if no exception occurred
if (ProcessingException != null || vlansResult == null)
{
return;
}
var existingVlans = vlansResult.Rows;
// Create a list to store the created VLANs
var createdVlans = new List<PSObject>();
// Get DHCP service if needed
DHCPService dhcpService = null;
if (EnableDHCP.IsPresent)
{
dhcpService = new DHCPService(ApiClient, Logger);
}
// Divide the network into subnets
var subnets = ipNetwork.Subnet((byte)SubnetMaskBits);
int vlanId = StartingVlanId;
int subnetIndex = 0;
foreach (var subnet in subnets)
{
// Calculate the gateway IP (first host address)
var gatewayIp = GetFirstHostAddress(subnet);
// Calculate the DHCP range (second host address to last host address)
var dhcpStart = GetSecondHostAddress(subnet);
var dhcpEnd = GetLastHostAddress(subnet);
// Create a description for the VLAN
string description = $"{DescriptionPrefix} {vlanId} - {subnet}";
// Check if the VLAN already exists
var existingVlan = existingVlans.FirstOrDefault(v =>
v.Interface == ParentInterface &&
int.Parse(v.Tag) == vlanId);
if (existingVlan != null)
{
WriteError(new ErrorRecord(
new ArgumentException($"Subnet mask bits ({SubnetMaskBits}) must be greater than the network CIDR ({ipNetwork.Cidr})."),
"InvalidSubnetMaskBits",
ErrorCategory.InvalidArgument,
SubnetMaskBits));
return;
WriteVerbose($"VLAN {vlanId} already exists on interface {ParentInterface}");
// Add the existing VLAN to the list
var vlanInfo = new PSObject();
vlanInfo.Properties.Add(new PSNoteProperty("VlanId", vlanId));
vlanInfo.Properties.Add(new PSNoteProperty("Subnet", subnet.ToString()));
vlanInfo.Properties.Add(new PSNoteProperty("Gateway", gatewayIp.ToString()));
vlanInfo.Properties.Add(new PSNoteProperty("Uuid", existingVlan.Uuid));
vlanInfo.Properties.Add(new PSNoteProperty("Status", "Existing"));
createdVlans.Add(vlanInfo);
}
// Calculate the number of subnets
int numSubnets = (int)Math.Pow(2, SubnetMaskBits - ipNetwork.Cidr);
// Calculate the maximum VLAN ID
int maxVlanId = StartingVlanId + (numSubnets - 1) * VlanIdIncrement;
// Validate the maximum VLAN ID
if (maxVlanId > 4094)
else
{
WriteError(new ErrorRecord(
new ArgumentException($"The maximum VLAN ID ({maxVlanId}) exceeds the maximum allowed value (4094)."),
"InvalidVlanIdRange",
ErrorCategory.InvalidArgument,
maxVlanId));
return;
}
// Get confirmation
if (!Force.IsPresent && !ShouldProcess($"Create {numSubnets} VLANs for subnets of {Network} with VLAN IDs {StartingVlanId}-{maxVlanId}", "New-OPNSenseSubnetVLANs"))
{
return;
}
// Get the interface service
var interfaceService = new InterfaceService(ApiClient, Logger);
// Verify the parent interface exists
var getInterfaceTask = Task.Run(async () => await interfaceService.GetInterfaceDetailAsync(ParentInterface));
getInterfaceTask.GetAwaiter().GetResult();
// Get existing VLANs
var getVlansTask = Task.Run(async () => await interfaceService.GetVLANsAsync());
var existingVlans = getVlansTask.GetAwaiter().GetResult().Rows;
// Create a list to store the created VLANs
var createdVlans = new List<PSObject>();
// Get DHCP service if needed
DHCPService dhcpService = null;
if (EnableDHCP.IsPresent)
{
dhcpService = new DHCPService(ApiClient, Logger);
}
// Divide the network into subnets
var subnets = ipNetwork.Subnet((byte)SubnetMaskBits);
int vlanId = StartingVlanId;
int subnetIndex = 0;
foreach (var subnet in subnets)
{
// Calculate the gateway IP (first host address)
var gatewayIp = GetFirstHostAddress(subnet);
// Calculate the DHCP range (second host address to last host address)
var dhcpStart = GetSecondHostAddress(subnet);
var dhcpEnd = GetLastHostAddress(subnet);
// Create a description for the VLAN
string description = $"{DescriptionPrefix} {vlanId} - {subnet}";
// Check if the VLAN already exists
var existingVlan = existingVlans.FirstOrDefault(v =>
v.Interface == ParentInterface &&
int.Parse(v.Tag) == vlanId);
if (existingVlan != null)
// Create the VLAN
var vlanConfig = new VLANConfig
{
WriteVerbose($"VLAN {vlanId} already exists on interface {ParentInterface}");
Interface = ParentInterface,
Tag = vlanId.ToString(),
Priority = "0",
Description = description
};
// Add the existing VLAN to the list
var vlanInfo = new PSObject();
vlanInfo.Properties.Add(new PSNoteProperty("VlanId", vlanId));
vlanInfo.Properties.Add(new PSNoteProperty("Subnet", subnet.ToString()));
vlanInfo.Properties.Add(new PSNoteProperty("Gateway", gatewayIp.ToString()));
vlanInfo.Properties.Add(new PSNoteProperty("Uuid", existingVlan.Uuid));
vlanInfo.Properties.Add(new PSNoteProperty("Status", "Existing"));
var createVlanResult = ExecuteAsyncTask(() => interfaceService.CreateVLANAsync(vlanConfig));
createdVlans.Add(vlanInfo);
}
else
// Only continue if no exception occurred
if (ProcessingException != null || createVlanResult == null)
{
// Create the VLAN
var vlanConfig = new VLANConfig
{
Interface = ParentInterface,
Tag = vlanId.ToString(),
Priority = "0",
Description = description
};
var createVlanTask = Task.Run(async () => await interfaceService.CreateVLANAsync(vlanConfig));
var createVlanResult = createVlanTask.GetAwaiter().GetResult();
WriteVerbose($"Created VLAN {vlanId} on interface {ParentInterface} with UUID {createVlanResult.Uuid}");
// Configure the VLAN interface
string vlanInterfaceName = $"{ParentInterface}.{vlanId}";
var interfaceConfig = new InterfaceConfig
{
Description = description,
IpAddress = gatewayIp.ToString(),
SubnetMask = SubnetMaskBits.ToString(),
Enabled = "1"
};
var updateInterfaceTask = Task.Run(async () => await interfaceService.UpdateInterfaceAsync(vlanInterfaceName, interfaceConfig));
updateInterfaceTask.GetAwaiter().GetResult();
WriteVerbose($"Configured interface {vlanInterfaceName} with IP {gatewayIp}/{SubnetMaskBits}");
// If EnableDHCP is specified, configure DHCP for the interface
if (EnableDHCP.IsPresent && dhcpService != null)
{
// Configure DHCP server for the interface
var dhcpConfig = new DHCPServerConfig
{
Enabled = "1",
RangeFrom = dhcpStart.ToString(),
RangeTo = dhcpEnd.ToString(),
DefaultLeaseTime = "7200",
MaxLeaseTime = "86400",
Domain = Domain,
Gateway = gatewayIp.ToString(),
DnsServers = DnsServers != null ? new List<string>(DnsServers) : new List<string> { gatewayIp.ToString() }
};
var updateDhcpTask = Task.Run(async () => await dhcpService.UpdateServerAsync(vlanInterfaceName, dhcpConfig));
updateDhcpTask.GetAwaiter().GetResult();
WriteVerbose($"Configured DHCP server for interface {vlanInterfaceName} with range {dhcpStart} - {dhcpEnd}");
}
// Add the created VLAN to the list
var vlanInfo = new PSObject();
vlanInfo.Properties.Add(new PSNoteProperty("VlanId", vlanId));
vlanInfo.Properties.Add(new PSNoteProperty("Subnet", subnet.ToString()));
vlanInfo.Properties.Add(new PSNoteProperty("Gateway", gatewayIp.ToString()));
vlanInfo.Properties.Add(new PSNoteProperty("Uuid", createVlanResult.Uuid));
vlanInfo.Properties.Add(new PSNoteProperty("Status", "Created"));
if (EnableDHCP.IsPresent)
{
vlanInfo.Properties.Add(new PSNoteProperty("DHCPRange", $"{dhcpStart} - {dhcpEnd}"));
}
createdVlans.Add(vlanInfo);
return;
}
// Increment the VLAN ID
vlanId += VlanIdIncrement;
subnetIndex++;
WriteVerbose($"Created VLAN {vlanId} on interface {ParentInterface} with UUID {createVlanResult.Uuid}");
// Configure the VLAN interface
string vlanInterfaceName = $"{ParentInterface}.{vlanId}";
var interfaceConfig = new InterfaceConfig
{
Description = description,
IpAddress = gatewayIp.ToString(),
SubnetMask = SubnetMaskBits.ToString(),
Enabled = "1"
};
var updateInterfaceResult = ExecuteAsyncTask(() => interfaceService.UpdateInterfaceAsync(vlanInterfaceName, interfaceConfig));
// Only continue if no exception occurred
if (ProcessingException != null)
{
return;
}
WriteVerbose($"Configured interface {vlanInterfaceName} with IP {gatewayIp}/{SubnetMaskBits}");
// If EnableDHCP is specified, configure DHCP for the interface
if (EnableDHCP.IsPresent && dhcpService != null)
{
// Configure DHCP server for the interface
var dhcpConfig = new DHCPServerConfig
{
Enabled = "1",
RangeFrom = dhcpStart.ToString(),
RangeTo = dhcpEnd.ToString(),
DefaultLeaseTime = "7200",
MaxLeaseTime = "86400",
Domain = Domain,
Gateway = gatewayIp.ToString(),
DnsServers = DnsServers != null ? new List<string>(DnsServers) : new List<string> { gatewayIp.ToString() }
};
var updateDhcpResult = ExecuteAsyncTask(() => dhcpService.UpdateServerAsync(vlanInterfaceName, dhcpConfig));
// Only continue if no exception occurred
if (ProcessingException != null)
{
return;
}
WriteVerbose($"Configured DHCP server for interface {vlanInterfaceName} with range {dhcpStart} - {dhcpEnd}");
}
// Add the created VLAN to the list
var vlanInfo = new PSObject();
vlanInfo.Properties.Add(new PSNoteProperty("VlanId", vlanId));
vlanInfo.Properties.Add(new PSNoteProperty("Subnet", subnet.ToString()));
vlanInfo.Properties.Add(new PSNoteProperty("Gateway", gatewayIp.ToString()));
vlanInfo.Properties.Add(new PSNoteProperty("Uuid", createVlanResult.Uuid));
vlanInfo.Properties.Add(new PSNoteProperty("Status", "Created"));
if (EnableDHCP.IsPresent)
{
vlanInfo.Properties.Add(new PSNoteProperty("DHCPRange", $"{dhcpStart} - {dhcpEnd}"));
}
createdVlans.Add(vlanInfo);
}
// Apply DHCP changes if needed
if (EnableDHCP.IsPresent && dhcpService != null)
{
var applyDhcpTask = Task.Run(async () => await dhcpService.ApplyChangesAsync());
var applyDhcpResult = applyDhcpTask.GetAwaiter().GetResult();
WriteVerbose($"Applied DHCP changes: {applyDhcpResult.Status}");
}
// Create a result object
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("ParentInterface", ParentInterface));
result.Properties.Add(new PSNoteProperty("Network", Network));
result.Properties.Add(new PSNoteProperty("SubnetMaskBits", SubnetMaskBits));
result.Properties.Add(new PSNoteProperty("VLANs", createdVlans));
WriteObject(result);
// Increment the VLAN ID
vlanId += VlanIdIncrement;
subnetIndex++;
}
catch (Exception ex)
// Apply DHCP changes if needed
if (EnableDHCP.IsPresent && dhcpService != null)
{
HandleException(ex);
var applyDhcpResult = ExecuteAsyncTask(() => dhcpService.ApplyChangesAsync());
// Only continue if no exception occurred
if (ProcessingException != null || applyDhcpResult == null)
{
return;
}
WriteVerbose($"Applied DHCP changes: {applyDhcpResult.Status}");
}
// Create a result object
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("ParentInterface", ParentInterface));
result.Properties.Add(new PSNoteProperty("Network", Network));
result.Properties.Add(new PSNoteProperty("SubnetMaskBits", SubnetMaskBits));
result.Properties.Add(new PSNoteProperty("VLANs", createdVlans));
WriteObject(result);
}
/// <summary>
@@ -66,33 +66,32 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var userService = new UserService(ApiClient, Logger);
var user = new UserConfig
{
var userService = new UserService(ApiClient, Logger);
Username = Username,
Password = Password,
FullName = FullName,
Email = Email,
Disabled = Disabled.IsPresent ? "1" : "0",
Groups = Groups != null ? new List<string>(Groups) : new List<string>(),
Authorizations = Authorizations != null ? new List<string>(Authorizations) : new List<string>()
};
var user = new UserConfig
{
Username = Username,
Password = Password,
FullName = FullName,
Email = Email,
Disabled = Disabled.IsPresent ? "1" : "0",
Groups = Groups != null ? new List<string>(Groups) : new List<string>(),
Authorizations = Authorizations != null ? new List<string>(Authorizations) : new List<string>()
};
// Use our safe execution method
var result = ExecuteAsyncTask(() => userService.CreateUserAsync(user));
var task = Task.Run(async () => await userService.CreateUserAsync(user));
var result = task.GetAwaiter().GetResult();
WriteVerbose($"Created user with UUID {result.Uuid}");
WriteObject(result.Uuid);
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteVerbose($"Created user with UUID {result.Uuid}");
WriteObject(result.Uuid);
}
}
}
@@ -48,30 +48,29 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
protected override void ProcessRecordInternal()
{
try
var interfaceService = new InterfaceService(ApiClient, Logger);
var vlan = new VLANConfig
{
var interfaceService = new InterfaceService(ApiClient, Logger);
Interface = Interface,
Tag = Tag.ToString(),
Priority = Priority.ToString(),
Description = Description
};
var vlan = new VLANConfig
{
Interface = Interface,
Tag = Tag.ToString(),
Priority = Priority.ToString(),
Description = Description
};
// Use our safe execution method
var result = ExecuteAsyncTask(() => interfaceService.CreateVLANAsync(vlan));
var task = Task.Run(async () => await interfaceService.CreateVLANAsync(vlan));
var result = task.GetAwaiter().GetResult();
WriteVerbose($"Created VLAN with UUID {result.Uuid}");
WriteObject(result.Uuid);
}
catch (Exception ex)
// Only continue if no exception occurred
if (ProcessingException != null || result == null)
{
HandleException(ex);
return;
}
WriteVerbose($"Created VLAN with UUID {result.Uuid}");
WriteObject(result.Uuid);
}
}
}

Some files were not shown because too many files have changed in this diff Show More