20 Commits

Author SHA1 Message Date
GraceSolutions 3936e6bb5d Comment out DotNetFrameworkVersion in PSD1 files 2025-04-15 09:29:47 -04:00
GraceSolutions 7535135bfd Fix threading issue in cmdlets
- Fixed threading issue in cmdlets that was causing 'WriteObject and WriteError methods cannot be called from outside the overrides' error
- Changed module structure to use DLL as root module instead of nested module
- Updated configuration cmdlets to properly handle async operations
2025-04-15 09:24:51 -04:00
GraceSolutions 68e6819131 Update configuration management cmdlets
- Added Get-OPNSenseConfig cmdlet that retrieves the OPNSense configuration as an XML document
- Updated Restore-OPNSenseConfig to accept an XML document from the pipeline or as a parameter
- Changed Export-OPNSenseConfig and Import-OPNSenseConfig to use System.IO.FileInfo for the Path parameter
- Removed Backup-OPNSenseConfig cmdlet (use Get-OPNSenseConfig instead)
- Updated documentation and examples
2025-04-15 08:53:09 -04:00
GraceSolutions c423cca25a Rename Upgrade-OPNSenseFirmware to Start-OPNSenseFirmwareUpgrade to resolve cmdlet name conflict 2025-04-15 07:40:23 -04:00
GraceSolutions 91dc1a1f17 Fix cmdlet name conflict by renaming Upgrade-OPNSenseFirmware to Start-OPNSenseFirmwareUpgrade 2025-04-15 01:34:55 -04:00
GraceSolutions 7476e03319 Update PSM1 files to use Add-Type instead of Assembly.Load 2025-04-15 01:04:29 -04:00
GraceSolutions 7a2331bf74 Update Create-Release.ps1 to test module import 2025-04-15 00:53:57 -04:00
GraceSolutions 4e137e927e Update cmdlet names in PSD1 files to use proper PowerShell verb-noun format 2025-04-15 00:53:05 -04:00
GraceSolutions 412f46ec8d Update PSM1 file to properly load assemblies and let PowerShell handle cmdlet export 2025-04-15 00:44:55 -04:00
GraceSolutions 2916619bf3 Update documentation to mention the release script 2025-04-14 23:37:52 -04:00
GraceSolutions ad693cedcd Add script to create releases and clean up old zip files 2025-04-14 23:36:50 -04:00
GraceSolutions ab0c75ab85 Add release notes for v2025.04.14.2340 2025-04-14 23:32:47 -04:00
GraceSolutions ef21e5d359 Update version to 2025.04.14.2340 2025-04-14 23:31:25 -04:00
GraceSolutions fa5c666cbd Update cmdlet list in PSD1 files to include all cmdlets 2025-04-14 23:27:46 -04:00
GraceSolutions d5fe842cb7 Update version to 2025.04.14.2320 2025-04-14 23:21:56 -04:00
GraceSolutions 52a422d6cc Improve PSM1 file: use = for assignment and provide more detailed verbose output 2025-04-14 23:20:15 -04:00
GraceSolutions ccfb37aaac Update Tailscale-Integration.md to include Connect-OPNSenseTailscale and Disconnect-OPNSenseTailscale cmdlets 2025-04-14 23:15:51 -04:00
GraceSolutions 28546595bd Update Visual-Studio-Solution.md to reflect current project structure 2025-04-14 23:12:54 -04:00
GraceSolutions 08e21843e0 Clean up repository: remove GitHub workflow, update .gitignore, remove unused files, update module structure 2025-04-14 23:07:22 -04:00
GraceSolutions bfbc031a76 Fix warnings, update version format, organize DLLs in subfolder, and use System.Reflection.Assembly::LoadBytes 2025-04-14 22:57:09 -04:00
40 changed files with 1462 additions and 1131 deletions
-126
View File
@@ -1,126 +0,0 @@
name: Build and Release
on:
push:
branches: [ main ]
paths-ignore:
- '**.md'
- 'docs/**'
- '.github/**'
- '!.github/workflows/build-and-release.yml'
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
release:
description: 'Create a release'
required: false
default: false
type: boolean
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'
- name: Setup PowerShell
uses: actions/setup-powershell@v1
with:
powershell-version: '7.2'
- name: Restore dependencies
run: dotnet restore src/PSOPNSenseAPI.sln
- name: Run tests
run: pwsh -Command "./build/test.ps1 -Configuration Release"
- name: Build and package
run: |
pwsh -Command "./build/build-release.ps1 -Clean -Package"
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: PSOPNSenseAPI
path: output/PSOPNSenseAPI/
- name: Upload package artifacts
uses: actions/upload-artifact@v3
with:
name: PSOPNSenseAPI-Package
path: packages/*.zip
release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event.inputs.release == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup PowerShell
uses: actions/setup-powershell@v1
with:
powershell-version: '7.2'
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: PSOPNSenseAPI
path: output/PSOPNSenseAPI/
- name: Download package artifacts
uses: actions/download-artifact@v3
with:
name: PSOPNSenseAPI-Package
path: packages/
- name: Configure Git
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
$version = Get-Date -Format "yyyy.MM.dd.HHmm"
# Update version in project file
$projectFile = "src/PSOPNSenseAPI/PSOPNSenseAPI.csproj"
$projectXml = [xml](Get-Content $projectFile)
$versionNode = $projectXml.Project.PropertyGroup.Version
$versionNode.InnerText = $version
$projectXml.Save($projectFile)
# Update version in module manifest
$manifestFile = "PSOPNSenseAPI/PSOPNSenseAPI.psd1"
$manifestContent = Get-Content -Path $manifestFile -Raw
$manifestContent = $manifestContent -replace "ModuleVersion\s*=\s*['`"].*['`"]", "ModuleVersion = '$version'"
Set-Content -Path $manifestFile -Value $manifestContent
# Commit changes
git add $projectFile
git add $manifestFile
git commit -m "Release version $version [skip ci]"
git push
# Create release
$packagePath = Get-ChildItem -Path "packages/*.zip" | Select-Object -First 1 -ExpandProperty FullName
gh release create "v$version" `
--title "Release v$version" `
--notes "Release version $version - $(Get-Date -Format 'yyyy-MM-dd')" `
$packagePath
shell: pwsh
+2
View File
@@ -1,3 +1,5 @@
.vs/
bin/
obj/
*.zip
output/*.zip
+15 -1
View File
@@ -5,7 +5,21 @@ All notable changes to the PSOPNSenseAPI module will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [2025.04.15.1143] - 2025-04-15
### Added
- Added `Get-OPNSenseConfig` cmdlet that retrieves the OPNSense configuration as an XML document
### Changed
- Renamed `Upgrade-OPNSenseFirmware` to `Start-OPNSenseFirmwareUpgrade` to resolve cmdlet name conflict
- Updated `Restore-OPNSenseConfig` to accept an XML document from the pipeline or as a parameter
- Changed `Export-OPNSenseConfig` and `Import-OPNSenseConfig` to use System.IO.FileInfo for the Path parameter
- Added unit tests for firmware management cmdlets
- Fixed threading issue in cmdlets that was causing "WriteObject and WriteError methods cannot be called from outside the overrides" error
- Changed module structure to use DLL as root module instead of nested module
### Removed
- Removed `Backup-OPNSenseConfig` cmdlet (use `Get-OPNSenseConfig` instead)
## [0.6.0] - 2025-04-14
-208
View File
@@ -1,208 +0,0 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'PSOPNSenseAPI.psm1'
# Version number of this module.
ModuleVersion = '2025.04.14.1839'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
# ID used to uniquely identify this module
GUID = '8f7a3d7a-0e5a-4b7c-9b5a-9c5b3a5a8e7a'
# 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 = ''
# 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 = @('bin\PSOPNSenseAPI.dll')
# 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 = @('bin\PSOPNSenseAPI.dll')
# 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 = @(
# Connection Management
'Connect-OPNSense',
'Disconnect-OPNSense',
'Get-OPNSenseConnection',
# Firewall Rule Management
'Get-OPNSenseFirewallRule',
'New-OPNSenseFirewallRule',
'Set-OPNSenseFirewallRule',
'Remove-OPNSenseFirewallRule',
'Enable-OPNSenseFirewallRule',
'Disable-OPNSenseFirewallRule',
'Apply-OPNSenseFirewallChanges',
# Alias Management
'Get-OPNSenseAlias',
'New-OPNSenseAlias',
'Set-OPNSenseAlias',
'Remove-OPNSenseAlias',
# NAT Rule Management
'Get-OPNSenseNATRule',
'New-OPNSenseNATRule',
'Set-OPNSenseNATRule',
'Remove-OPNSenseNATRule',
# Interface Management
'Get-OPNSenseInterface',
'Set-OPNSenseInterface',
'Restart-OPNSenseInterface',
'Get-OPNSenseInterfaceStatistics',
'New-OPNSenseSubnetVLANs',
# VLAN Management
'Get-OPNSenseVLAN',
'New-OPNSenseVLAN',
'Set-OPNSenseVLAN',
'Remove-OPNSenseVLAN',
# DNS Configuration
'Get-OPNSenseDNSServer',
'Set-OPNSenseDNSServer',
'Get-OPNSenseDNSDomain',
'Set-OPNSenseDNSDomain',
'Get-OPNSenseDNSOverride',
'New-OPNSenseDNSOverride',
'Remove-OPNSenseDNSOverride',
# Configuration Management
'Backup-OPNSenseConfig',
'Restore-OPNSenseConfig',
'Export-OPNSenseConfig',
'Import-OPNSenseConfig',
'Get-OPNSenseConfigBackup',
# Plugin Management
'Get-OPNSensePlugin',
'Install-OPNSensePlugin',
'Uninstall-OPNSensePlugin',
'Enable-OPNSensePlugin',
'Disable-OPNSensePlugin',
# User Management
'Get-OPNSenseUser',
'New-OPNSenseUser',
'Set-OPNSenseUser',
'Remove-OPNSenseUser',
# Firmware Management
'Get-OPNSenseFirmware',
'Update-OPNSenseFirmware',
'Upgrade-OPNSenseFirmware',
# System Management
'Restart-OPNSenseFirewall'
)
# 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 = @('OPNSense', 'Firewall', 'API', 'Network', 'Security')
# 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 = 'Initial release of the PSOPNSenseAPI module'
# 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 = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
-3
View File
@@ -1,3 +0,0 @@
# This file is intentionally left empty.
# The module functionality is provided by the binary module (DLL).
# This file exists to allow for future PowerShell script functions if needed.
+8 -3
View File
@@ -135,12 +135,17 @@ Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/24" -Operation SupernetSumm
# Firmware management
Update-OPNSenseFirmware -Wait
# Major firmware upgrade
Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200
# Reboot firewall
Restart-OPNSenseFirewall -Wait -Timeout 300
# Backup and restore configuration
Backup-OPNSenseConfig -Filename "pre-upgrade-backup"
Export-OPNSenseConfig -Path "C:\Backups\opnsense-config.xml"
# 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
+43
View File
@@ -0,0 +1,43 @@
# Release v2025.04.14.2340
## What's New
### Added
- Port forwarding management (Get, New, Set, Remove)
- Added unit tests for interface management
- Improved XML documentation for all classes and methods
### Changed
- Updated IPNetwork2 library from version 2.6.618 to 3.1.764
- Updated code to use the new namespace (System.Net) for IPNetwork2
- Added support for .NET 8.0 and .NET 9.0
- Made OPNSenseApiClient more testable by introducing an interface
- Fixed warnings in test project
- Updated version format to use datetime (yyyy.MM.dd.HHmm)
- Organized DLLs in a subfolder for neatness
- Used System.Reflection.Assembly::LoadBytes to avoid file lock issues
- Removed GitHub workflow
- Updated documentation for Tailscale integration
- Updated Visual Studio solution documentation
- Updated PSD1 files to include all cmdlets
## Installation
1. Download the release from the GitHub releases page: https://github.com/freedbygrace/PSOPNSenseAPI/releases/tag/v2025.04.14.2340
2. Extract the zip file to a folder in your PowerShell module path
3. Import the module: `Import-Module PSOPNSenseAPI`
## Usage
```powershell
# Connect to the OPNSense firewall
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
# Get the interfaces
Get-OPNSenseInterface
# Disconnect from the firewall
Disconnect-OPNSense
```
For more examples, see the documentation in the `docs` folder.
+87 -8
View File
@@ -6,6 +6,8 @@ This component provides cmdlets for managing Tailscale VPN on OPNSense firewalls
The Tailscale Integration component allows you to install, configure, and manage Tailscale VPN on OPNSense firewalls. It provides cmdlets for enabling Tailscale, configuring subnet routes, and managing the Tailscale connection.
The module will automatically install the Tailscale plugin if it doesn't exist, making it easy to set up Tailscale on a new OPNSense firewall.
## Cmdlets
### Get-OPNSenseTailscaleStatus
@@ -138,6 +140,53 @@ Apply-OPNSenseTailscaleChanges
Apply-OPNSenseTailscaleChanges -Force
```
### Connect-OPNSenseTailscale
Connects to the Tailscale network using an authentication key.
#### Parameters
- **AuthKey** - The Tailscale authentication key.
- **InstallIfMissing** - Installs the Tailscale plugin if it's not already installed.
- **EnableIfDisabled** - Enables Tailscale if it's disabled.
- **StartIfStopped** - Starts the Tailscale service if it's stopped.
- **SubnetRoutes** - The subnet routes to advertise to Tailscale.
- **AdvertiseRoutes** - Whether to advertise routes to Tailscale.
- **Force** - Suppresses the confirmation prompt.
- **PassThru** - Returns the Tailscale status.
#### Examples
```powershell
# Connect to Tailscale with an auth key
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456"
# Connect to Tailscale with automatic installation and enablement
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -InstallIfMissing -EnableIfDisabled -StartIfStopped
# Connect to Tailscale and advertise subnet routes
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -SubnetRoutes "192.168.1.0/24","10.0.0.0/8" -AdvertiseRoutes
```
### Disconnect-OPNSenseTailscale
Disconnects from the Tailscale network.
#### Parameters
- **Force** - Suppresses the confirmation prompt.
- **PassThru** - Returns the Tailscale status.
#### Examples
```powershell
# Disconnect from Tailscale
Disconnect-OPNSenseTailscale
# Disconnect from Tailscale without confirmation
Disconnect-OPNSenseTailscale -Force
```
## Common Scenarios
### Installing and Configuring Tailscale
@@ -159,6 +208,19 @@ Apply-OPNSenseTailscaleChanges -Force
Disconnect-OPNSense
```
### Simplified Installation and Connection
```powershell
# Connect to the OPNSense firewall
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
# Connect to Tailscale with automatic installation and enablement
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -InstallIfMissing -EnableIfDisabled -StartIfStopped -Force
# Disconnect from the firewall
Disconnect-OPNSense
```
### Configuring Tailscale as a Subnet Router
```powershell
@@ -177,14 +239,8 @@ foreach ($interface in $interfaces) {
}
}
# Join the subnets with commas
$advertisedRoutes = $subnets -join ","
# Enable Tailscale with route advertisement
Enable-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -AdvertiseRoutes $advertisedRoutes -Force
# Apply the changes
Apply-OPNSenseTailscaleChanges -Force
# Connect to Tailscale with subnet route advertisement
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -SubnetRoutes $subnets -AdvertiseRoutes -InstallIfMissing -Force
# Disconnect from the firewall
Disconnect-OPNSense
@@ -206,6 +262,26 @@ Apply-OPNSenseTailscaleChanges -Force
Disconnect-OPNSense
```
### Connecting and Disconnecting from Tailscale
```powershell
# Connect to the OPNSense firewall
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
# Connect to Tailscale
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -InstallIfMissing -EnableIfDisabled -StartIfStopped -Force
# Get Tailscale status
$status = Get-OPNSenseTailscaleStatus
Write-Output "Tailscale Status: $($status.Status)"
# Disconnect from Tailscale
Disconnect-OPNSenseTailscale -Force
# Disconnect from the firewall
Disconnect-OPNSense
```
### Updating Tailscale Routes
```powershell
@@ -253,6 +329,7 @@ Disconnect-OPNSense
- Tailscale requires the Tailscale plugin to be installed on the OPNSense firewall.
- The `Install-OPNSenseTailscale` cmdlet will install the plugin if it's not already installed.
- The `Connect-OPNSenseTailscale` cmdlet can automatically install the plugin, enable Tailscale, and start the service with the `-InstallIfMissing`, `-EnableIfDisabled`, and `-StartIfStopped` parameters.
- Tailscale authentication keys can be generated from the Tailscale admin console.
- Authentication keys are single-use and expire after a short period.
- When advertising routes, ensure that the routes are valid and do not overlap with Tailscale's internal routes.
@@ -261,3 +338,5 @@ Disconnect-OPNSense
- The `ExitNode` option allows other Tailscale nodes to use the OPNSense firewall as an internet gateway.
- Changes to Tailscale settings are not applied until you call `Apply-OPNSenseTailscaleChanges`.
- Consider using the `-PassThru` parameter when updating Tailscale settings to verify the changes.
- The `Connect-OPNSenseTailscale` cmdlet combines multiple steps (installation, enablement, and connection) into a single command.
- The `Disconnect-OPNSenseTailscale` cmdlet disconnects from the Tailscale network but does not disable Tailscale.
+45 -10
View File
@@ -11,6 +11,7 @@ The PSOPNSenseAPI solution is a Visual Studio solution that contains the source
The solution contains the following projects:
- **PSOPNSenseAPI**: The main project containing the PowerShell module code.
- **PSOPNSenseAPI.Tests**: The test project containing unit tests for the PowerShell module.
## Opening the Solution
@@ -56,19 +57,19 @@ If you encounter issues with the IPNetwork2 library, here are some common troubl
1. **Check the reference**: Ensure that the IPNetwork2 package is properly referenced in the project file.
```xml
<PackageReference Include="IPNetwork2" Version="2.6.618" />
<PackageReference Include="IPNetwork2" Version="3.1.764" />
```
2. **Verify the namespace**: The correct namespace for IPNetwork2 is `System.Net.IPNetwork`.
2. **Verify the namespace**: The correct namespace for IPNetwork2 is `System.Net`.
```csharp
using System.Net.IPNetwork;
using System.Net;
```
3. **Check the class name**: The main class in IPNetwork2 is `IPNetwork`, not `IPNetwork2`.
```csharp
// Correct usage
System.Net.IPNetwork.IPNetwork network = System.Net.IPNetwork.IPNetwork.Parse("192.168.1.0/24");
System.Net.IPNetwork network = System.Net.IPNetwork.Parse("192.168.1.0/24");
// Incorrect usage
IPNetwork2 network = IPNetwork2.Parse("192.168.1.0/24");
```
@@ -100,7 +101,7 @@ When adding new features to the solution:
2. Implement service classes in the `Services` directory
3. Create PowerShell cmdlets in the `Cmdlets` directory
4. Update the module manifest (`PSOPNSenseAPI.psd1`) to export the new cmdlets
5. Add tests for the new features
5. Add tests for the new features in the `PSOPNSenseAPI.Tests` project
6. Update documentation
## Testing
@@ -112,9 +113,24 @@ The solution includes unit tests that can be run from Visual Studio using the Te
.\build\test.ps1
```
## Continuous Integration
## Release Process
The project uses GitHub Actions for continuous integration. The CI workflow builds and tests the solution on every push to the main branch.
The project includes a script to automate the release process. To create a new release:
1. Run the `scripts\Create-Release.ps1` script
2. Follow the instructions to create a GitHub release
The script will:
1. Update the version in the project file and module manifest to the current date and time (yyyy.MM.dd.HHmm)
2. Build the solution in Release mode
3. Copy the DLLs to the output folder
4. Create a zip file of the module
5. Clean up old zip files, keeping only the latest one
6. Commit and push the changes
7. Create a release note file
After running the script, you'll need to manually create a GitHub release and attach the zip file.
## Dependencies
@@ -123,10 +139,29 @@ The solution has the following dependencies:
- PowerShellStandard.Library (5.1.1)
- Newtonsoft.Json (13.0.3)
- System.Net.Http (4.3.4)
- IPNetwork2 (2.6.618)
- IPNetwork2 (3.1.764)
## Notes
- The solution is configured to target both .NET Framework 4.7.2 and .NET Standard 2.0 to support both Windows PowerShell 5.1 and PowerShell 7+.
- The module uses a versioning scheme of `yyyy.MM.dd.HHmm` for releases, which is automatically generated during the build process.
- The module uses a versioning scheme of `yyyy.MM.dd.HHmm` for releases.
- The solution includes XML documentation comments that are used to generate help content for the PowerShell cmdlets.
- The module uses System.Reflection.Assembly::LoadBytes to load DLLs to avoid file lock issues.
- DLLs are organized in a lib subfolder for neatness.
## Module Structure
The module has the following structure:
```
PSOPNSenseAPI/
├── lib/ # DLL files
│ ├── Newtonsoft.Json.dll
│ ├── PSOPNSenseAPI.dll
│ ├── System.Net.IPNetwork.dll
│ └── ...
├── PSOPNSenseAPI.psd1 # Module manifest
└── PSOPNSenseAPI.psm1 # Module script that loads DLLs
```
The PSM1 file uses System.Reflection.Assembly::LoadBytes to load the DLLs from the lib folder, which avoids file lock issues that can occur when PowerShell loads DLLs directly.
+76
View File
@@ -0,0 +1,76 @@
# Export-OPNSenseConfig
## SYNOPSIS
Exports the OPNSense firewall configuration.
## SYNTAX
```
Export-OPNSenseConfig -Path <FileInfo> [-Force]
```
## DESCRIPTION
The Export-OPNSenseConfig cmdlet exports the OPNSense firewall configuration to a file.
## EXAMPLES
### Example 1: Export the configuration to a file
```powershell
$path = New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml"
Export-OPNSenseConfig -Path $path
```
This example exports the OPNSense firewall configuration to a file.
## PARAMETERS
### -Path
The path to save the configuration file. Must be a FileInfo object with an .xml extension.
```yaml
Type: FileInfo
Parameter Sets: (All)
Aliases:
Required: True
Position: 0
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -Force
Overwrites the file if it exists.
```yaml
Type: SwitchParameter
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### CommonParameters
This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
## INPUTS
### None
## OUTPUTS
### None
## NOTES
This cmdlet requires a connection to an OPNSense firewall. Use Connect-OPNSense to establish a connection.
If the directory specified in the Path parameter does not exist, it will be created.
## RELATED LINKS
[Get-OPNSenseConfig](Get-OPNSenseConfig.md)
[Import-OPNSenseConfig](Import-OPNSenseConfig.md)
[Restore-OPNSenseConfig](Restore-OPNSenseConfig.md)
+52
View File
@@ -0,0 +1,52 @@
# Get-OPNSenseConfig
## SYNOPSIS
Gets the OPNSense firewall configuration as an XML document.
## SYNTAX
```
Get-OPNSenseConfig
```
## DESCRIPTION
The Get-OPNSenseConfig cmdlet retrieves the OPNSense firewall configuration and returns it as an XML document.
## EXAMPLES
### Example 1: Get the configuration as an XML document
```powershell
$config = Get-OPNSenseConfig
```
This example retrieves the OPNSense firewall configuration as an XML document.
### Example 2: Get the configuration and pipe it to Restore-OPNSenseConfig
```powershell
Get-OPNSenseConfig | Restore-OPNSenseConfig
```
This example retrieves the OPNSense firewall configuration and restores it.
## PARAMETERS
### CommonParameters
This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
## INPUTS
### None
## OUTPUTS
### System.Xml.XmlDocument
The OPNSense configuration as an XML document.
## NOTES
This cmdlet requires a connection to an OPNSense firewall. Use Connect-OPNSense to establish a connection.
## RELATED LINKS
[Restore-OPNSenseConfig](Restore-OPNSenseConfig.md)
[Export-OPNSenseConfig](Export-OPNSenseConfig.md)
[Import-OPNSenseConfig](Import-OPNSenseConfig.md)
+106
View File
@@ -0,0 +1,106 @@
# Import-OPNSenseConfig
## SYNOPSIS
Imports an OPNSense firewall configuration.
## SYNTAX
```
Import-OPNSenseConfig -Path <FileInfo> [-Force] [-WhatIf] [-Confirm]
```
## DESCRIPTION
The Import-OPNSenseConfig cmdlet imports an OPNSense firewall configuration from a file.
## EXAMPLES
### Example 1: Import a configuration from a file
```powershell
$path = New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml"
Import-OPNSenseConfig -Path $path
```
This example imports an OPNSense firewall configuration from a file.
## PARAMETERS
### -Path
The path to the configuration file. Must be a FileInfo object with an .xml extension.
```yaml
Type: FileInfo
Parameter Sets: (All)
Aliases:
Required: True
Position: 0
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -Force
Suppresses the confirmation prompt.
```yaml
Type: SwitchParameter
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -WhatIf
Shows what would happen if the cmdlet runs. The cmdlet is not run.
```yaml
Type: SwitchParameter
Parameter Sets: (All)
Aliases: wi
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -Confirm
Prompts you for confirmation before running the cmdlet.
```yaml
Type: SwitchParameter
Parameter Sets: (All)
Aliases: cf
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### CommonParameters
This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
## INPUTS
### None
## OUTPUTS
### None
## NOTES
This cmdlet requires a connection to an OPNSense firewall. Use Connect-OPNSense to establish a connection.
The firewall will restart after the configuration is imported.
## RELATED LINKS
[Get-OPNSenseConfig](Get-OPNSenseConfig.md)
[Export-OPNSenseConfig](Export-OPNSenseConfig.md)
[Restore-OPNSenseConfig](Restore-OPNSenseConfig.md)
+142
View File
@@ -0,0 +1,142 @@
# Restore-OPNSenseConfig
## SYNOPSIS
Restores an OPNSense firewall configuration.
## SYNTAX
### Filename
```
Restore-OPNSenseConfig -Filename <String> [-Force] [-WhatIf] [-Confirm]
```
### XmlDocument
```
Restore-OPNSenseConfig -XmlDocument <XmlDocument> [-Force] [-WhatIf] [-Confirm]
```
## DESCRIPTION
The Restore-OPNSenseConfig cmdlet restores an OPNSense firewall configuration from a backup or an XML document.
## EXAMPLES
### Example 1: Restore a configuration backup
```powershell
Restore-OPNSenseConfig -Filename "config-backup-20250414-1234.xml"
```
This example restores an OPNSense firewall configuration from a backup file.
### Example 2: Restore a configuration from an XML document
```powershell
$config = Get-OPNSenseConfig
Restore-OPNSenseConfig -XmlDocument $config
```
This example restores an OPNSense firewall configuration from an XML document.
### Example 3: Restore a configuration from the pipeline
```powershell
Get-OPNSenseConfig | Restore-OPNSenseConfig
```
This example restores an OPNSense firewall configuration from the pipeline.
## PARAMETERS
### -Filename
The filename of the backup to restore.
```yaml
Type: String
Parameter Sets: Filename
Aliases:
Required: True
Position: 0
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -XmlDocument
The XML document containing the configuration to restore.
```yaml
Type: XmlDocument
Parameter Sets: XmlDocument
Aliases:
Required: True
Position: 0
Default value: None
Accept pipeline input: True (ByValue)
Accept wildcard characters: False
```
### -Force
Suppresses the confirmation prompt.
```yaml
Type: SwitchParameter
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -WhatIf
Shows what would happen if the cmdlet runs. The cmdlet is not run.
```yaml
Type: SwitchParameter
Parameter Sets: (All)
Aliases: wi
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -Confirm
Prompts you for confirmation before running the cmdlet.
```yaml
Type: SwitchParameter
Parameter Sets: (All)
Aliases: cf
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### CommonParameters
This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
## INPUTS
### System.Xml.XmlDocument
An XML document containing the OPNSense configuration.
## OUTPUTS
### None
## NOTES
This cmdlet requires a connection to an OPNSense firewall. Use Connect-OPNSense to establish a connection.
The firewall will restart after the configuration is restored.
## RELATED LINKS
[Get-OPNSenseConfig](Get-OPNSenseConfig.md)
[Export-OPNSenseConfig](Export-OPNSenseConfig.md)
[Import-OPNSenseConfig](Import-OPNSenseConfig.md)
+15 -8
View File
@@ -8,22 +8,29 @@ Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -
$backups = Get-OPNSenseConfigBackup
$backups | Format-Table -Property Filename, Description, FileSize, Date, Version
# Create a new configuration backup
$backupFilename = Backup-OPNSenseConfig -Filename "pre-upgrade-backup"
Write-Output "Created backup: $backupFilename"
# Get the current configuration as an XML document
$config = Get-OPNSenseConfig
Write-Output "Retrieved configuration with root element: $($config.DocumentElement.Name)"
# Export the configuration to a file
$exportPath = "C:\Backups\opnsense-config.xml"
$exportPath = New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml"
Export-OPNSenseConfig -Path $exportPath -Force
Write-Output "Exported configuration to: $exportPath"
Write-Output "Exported configuration to: $($exportPath.FullName)"
# Import a configuration from a file
# Note: This will restart the firewall
# Import-OPNSenseConfig -Path $exportPath -Confirm
# Import-OPNSenseConfig -Path $exportPath -Force
# Restore a configuration backup
# Restore a configuration backup by filename
# Note: This will restart the firewall
# Restore-OPNSenseConfig -Filename $backupFilename -Confirm
# Restore-OPNSenseConfig -Filename "config-backup-20250415-1234.xml" -Force
# Restore a configuration from an XML document
# Note: This will restart the firewall
# Restore-OPNSenseConfig -XmlDocument $config -Force
# Pipeline example: Get and restore configuration in one line
# Get-OPNSenseConfig | Restore-OPNSenseConfig -Force
# Disconnect from the firewall
Disconnect-OPNSense
+2 -1
View File
@@ -27,7 +27,8 @@ Write-Output "Firmware updated successfully"
# Upgrade firmware (major version upgrade)
# Note: This will restart the firewall
# Upgrade-OPNSenseFirmware -Wait -Timeout 1200
Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200
Write-Output "Firmware upgraded successfully"
# Restart the firewall
Restart-OPNSenseFirewall -Wait -Timeout 300
Binary file not shown.
+193 -65
View File
@@ -1,85 +1,213 @@
@{
RootModule = 'PSOPNSenseAPI.dll'
ModuleVersion = '2025.04.14.2246'
GUID = '1f0e4b77-cc7c-4a1e-b45a-d7c51a3c562e'
Author = 'PSOPNSenseAPI Contributors'
CompanyName = 'PSOPNSenseAPI'
Copyright = 'Copyright (c) 2025 PSOPNSenseAPI Contributors'
Description = 'PowerShell module for interacting with the OPNSense API to configure firewalls'
PowerShellVersion = '5.1'
# Script module or binary module file associated with this manifest.
RootModule = 'lib\PSOPNSenseAPI.dll'
# Version number of this module.
ModuleVersion = '2025.04.15.1143'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
DotNetFrameworkVersion = '4.7.2'
CLRVersion = '4.0.0'
# 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',
'Disconnect-OPNSense',
'Get-OPNSenseInterface',
'Set-OPNSenseInterface',
'Restart-OPNSenseInterface',
'Get-OPNSenseInterfaceStatistics',
'Get-OPNSenseVLAN',
'New-OPNSenseVLAN',
'Set-OPNSenseVLAN',
'Remove-OPNSenseVLAN',
'Get-OPNSenseFirewallRule',
'New-OPNSenseFirewallRule',
'Set-OPNSenseFirewallRule',
'Remove-OPNSenseFirewallRule',
'Enable-OPNSenseFirewallRule',
'Disable-OPNSenseFirewallRule',
'Get-OPNSenseAlias',
'New-OPNSenseAlias',
'Set-OPNSenseAlias',
'Remove-OPNSenseAlias',
'Get-OPNSenseNATRule',
'New-OPNSenseNATRule',
'Set-OPNSenseNATRule',
'Remove-OPNSenseNATRule',
'Get-OPNSensePlugin',
'Enable-OPNSensePlugin',
'Disable-OPNSensePlugin',
'Install-OPNSensePlugin',
'Uninstall-OPNSensePlugin',
'Get-OPNSenseUser',
'New-OPNSenseUser',
'Set-OPNSenseUser',
'Remove-OPNSenseUser',
'Get-OPNSenseFirmware',
'Update-OPNSenseFirmware',
'Upgrade-OPNSenseFirmware',
'Restart-OPNSenseFirewall',
'New-OPNSenseSubnetVLANs',
'Get-OPNSenseDHCPServer',
'Set-OPNSenseDHCPServer',
'Get-OPNSenseDHCPStaticMapping',
'New-OPNSenseDHCPStaticMapping',
'Set-OPNSenseDHCPStaticMapping',
'Remove-OPNSenseDHCPStaticMapping',
'Get-OPNSenseCronJob',
'New-OPNSenseCronJob',
'Set-OPNSenseCronJob',
'Remove-OPNSenseCronJob',
'Enable-OPNSenseCronJob',
'Disable-OPNSenseCronJob',
'Get-OPNSenseTailscale',
'Enable-OPNSenseTailscale',
'Disable-OPNSenseTailscale',
'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',
'Remove-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 = ''
}
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
## What's New
### Added
- Port forwarding management (Get, New, Set, Remove)
- Added unit tests for interface management
### Changed
- Updated IPNetwork2 library from version 2.6.618 to 3.1.764
- Updated code to use the new namespace (System.Net) for IPNetwork2
- Added support for .NET 8.0 and .NET 9.0
- Improved XML documentation for all classes and methods
- Made OPNSenseApiClient more testable by introducing an interface
+130
View File
@@ -0,0 +1,130 @@
# Create-Release.ps1
# This script creates a new release of the PSOPNSenseAPI module
# Get the current date and time in the format yyyy.MM.dd.HHmm
$version = Get-Date -Format "yyyy.MM.dd.HHmm"
# Update the version in the project file
$projectFile = "src\PSOPNSenseAPI\PSOPNSenseAPI.csproj"
$projectContent = Get-Content $projectFile
$projectContent = $projectContent -replace "<Version>.*</Version>", "<Version>$version</Version>"
Set-Content $projectFile $projectContent
# Update the version in the module manifest files
$sourcePsd1 = "src\PSOPNSenseAPI\PSOPNSenseAPI.psd1"
$sourcePsd1Content = Get-Content $sourcePsd1
$sourcePsd1Content = $sourcePsd1Content -replace "ModuleVersion = '.*'", "ModuleVersion = '$version'"
Set-Content $sourcePsd1 $sourcePsd1Content
$outputPsd1 = "output\PSOPNSenseAPI\PSOPNSenseAPI.psd1"
$outputPsd1Content = Get-Content $outputPsd1
$outputPsd1Content = $outputPsd1Content -replace "ModuleVersion = '.*'", "ModuleVersion = '$version'"
Set-Content $outputPsd1 $outputPsd1Content
# Build the project
dotnet build -c Release
# Copy the DLLs to the output folder
$libPath = "output\PSOPNSenseAPI\lib-new"
New-Item -Path $libPath -ItemType Directory -Force | Out-Null
Copy-Item -Path "src\PSOPNSenseAPI\bin\Release\net472\*.dll" -Destination $libPath
Remove-Item -Path "output\PSOPNSenseAPI\lib" -Recurse -Force -ErrorAction SilentlyContinue
Rename-Item -Path $libPath -NewName "lib"
# Create the PSM1 file
$psm1Path = "output\PSOPNSenseAPI\PSOPNSenseAPI.psm1"
$psm1Content = @"
# Load assemblies from the lib folder using System.Reflection.Assembly::LoadBytes
# This avoids file lock issues when the module is loaded
`$libPath = Join-Path `$PSScriptRoot "lib"
`$dllFiles = Get-ChildItem -Path `$libPath -Filter "*.dll"
foreach (`$dll in `$dllFiles) {
try {
Write-Verbose "Attempting to load assembly: `$(`$dll.FullName)"
`$dllBytes = [System.IO.File]::ReadAllBytes(`$dll.FullName)
`$Null = [System.Reflection.Assembly]::Load(`$dllBytes)
Write-Verbose "Successfully loaded assembly: `$(`$dll.FullName)"
}
catch {
Write-Warning "Failed to load assembly `$(`$dll.Name): `$_"
}
}
# PowerShell will automatically export the cmdlets based on the [Cmdlet] attribute
"@
Set-Content -Path $psm1Path -Value $psm1Content
# Test the module import
Write-Host "Testing module import..."
try {
Import-Module "$PSScriptRoot\..\output\PSOPNSenseAPI\PSOPNSenseAPI.psd1" -Force -Verbose
$cmdlets = Get-Command -Module PSOPNSenseAPI
Write-Host "Successfully imported module with $($cmdlets.Count) cmdlets"
Remove-Module PSOPNSenseAPI -Force -ErrorAction SilentlyContinue
}
catch {
Write-Error "Failed to import module: $_"
exit 1
}
# Create a release archive
$zipFile = "output\PSOPNSenseAPI-$version.zip"
Compress-Archive -Path "output\PSOPNSenseAPI\*" -DestinationPath $zipFile -Force
# Clean up old zip files
Get-ChildItem -Path "output" -Filter "*.zip" |
Where-Object { $_.Name -ne "PSOPNSenseAPI-$version.zip" } |
Remove-Item -Force
# Commit the changes
git add .
git commit -m "Update version to $version"
git push origin main
# Create a GitHub release
Write-Host "To create a GitHub release, go to https://github.com/freedbygrace/PSOPNSenseAPI/releases/new"
Write-Host "Tag version: v$version"
Write-Host "Release title: v$version"
Write-Host "Description: See CHANGELOG.md for details"
Write-Host "Attach the file: $zipFile"
# Create a release note file
$releaseNotes = @"
# Release v$version
## What's New
See [CHANGELOG.md](CHANGELOG.md) for details.
## Installation
1. Download the release from the GitHub releases page: https://github.com/freedbygrace/PSOPNSenseAPI/releases/tag/v$version
2. Extract the zip file to a folder in your PowerShell module path
3. Import the module: `Import-Module PSOPNSenseAPI`
## Usage
```powershell
# Connect to the OPNSense firewall
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
# Get the interfaces
Get-OPNSenseInterface
# Disconnect from the firewall
Disconnect-OPNSense
```
For more examples, see the documentation in the `docs` folder.
"@
Set-Content -Path "RELEASE.md" -Value $releaseNotes
# Commit the release notes
git add RELEASE.md
git commit -m "Add release notes for v$version"
git push origin main
Write-Host "Release created successfully!"
@@ -1,53 +0,0 @@
using System;
using System.Management.Automation;
using System.Threading.Tasks;
using PSOPNSenseAPI.Services;
namespace PSOPNSenseAPI.Cmdlets
{
/// <summary>
/// <para type="synopsis">Creates a backup of the OPNSense firewall configuration.</para>
/// <para type="description">The Backup-OPNSenseConfig cmdlet creates a backup of the OPNSense firewall configuration.</para>
/// <example>
/// <para>Example 1: Create a backup</para>
/// <code>Backup-OPNSenseConfig</code>
/// <para>This example creates a backup of the OPNSense firewall configuration with an automatically generated filename.</para>
/// </example>
/// <example>
/// <para>Example 2: Create a backup with a specific filename</para>
/// <code>Backup-OPNSenseConfig -Filename "pre-upgrade-backup"</code>
/// <para>This example creates a backup of the OPNSense firewall configuration with a specific filename.</para>
/// </example>
/// </summary>
[Cmdlet(VerbsData.Backup, "OPNSenseConfig")]
[OutputType(typeof(string))]
public class BackupOPNSenseConfigCmdlet : OPNSenseBaseCmdlet
{
/// <summary>
/// <para type="description">The filename for the backup.</para>
/// </summary>
[Parameter(Mandatory = false, Position = 0)]
public string Filename { get; set; }
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
{
try
{
var configService = new ConfigService(ApiClient, Logger);
var task = Task.Run(async () => await configService.CreateConfigBackupAsync(Filename));
var result = task.GetAwaiter().GetResult();
WriteVerbose($"Created configuration backup: {result.Filename}");
WriteObject(result.Filename);
}
catch (Exception ex)
{
HandleException(ex);
}
}
}
}
@@ -23,8 +23,8 @@ namespace PSOPNSenseAPI.Cmdlets
/// <para type="description">The path to save the configuration file.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0)]
[ValidateNotNullOrEmpty]
public string Path { get; set; }
[ValidateNotNull]
public FileInfo Path { get; set; }
/// <summary>
/// <para type="description">Overwrites the file if it exists.</para>
@@ -39,33 +39,35 @@ namespace PSOPNSenseAPI.Cmdlets
{
try
{
string fullPath = Path.FullName;
// Check if the file exists
if (File.Exists(Path) && !Force.IsPresent)
if (File.Exists(fullPath) && !Force.IsPresent)
{
WriteError(new ErrorRecord(
new IOException($"The file '{Path}' already exists. Use -Force to overwrite."),
new IOException($"The file '{fullPath}' already exists. Use -Force to overwrite."),
"FileExists",
ErrorCategory.ResourceExists,
Path));
fullPath));
return;
}
var configService = new ConfigService(ApiClient, Logger);
var task = Task.Run(async () => await configService.ExportConfigAsync());
var configContent = task.GetAwaiter().GetResult();
// 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(Path);
var directory = System.IO.Path.GetDirectoryName(fullPath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// Write the configuration to the file
File.WriteAllBytes(Path, configContent);
File.WriteAllBytes(fullPath, configContent);
WriteVerbose($"Exported configuration to {Path}");
WriteVerbose($"Exported configuration to {fullPath}");
}
catch (Exception ex)
{
@@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Management.Automation;
using System.Threading.Tasks;
using System.Xml;
using PSOPNSenseAPI.Services;
namespace PSOPNSenseAPI.Cmdlets
{
/// <summary>
/// <para type="synopsis">Gets the OPNSense firewall configuration as an XML document.</para>
/// <para type="description">The Get-OPNSenseConfig cmdlet retrieves the OPNSense firewall configuration and returns it as an XML document.</para>
/// <example>
/// <para>Example 1: Get the configuration as an XML document</para>
/// <code>$config = Get-OPNSenseConfig</code>
/// <para>This example retrieves the OPNSense firewall configuration as an XML document.</para>
/// </example>
/// <example>
/// <para>Example 2: Get the configuration and pipe it to Restore-OPNSenseConfig</para>
/// <code>Get-OPNSenseConfig | Restore-OPNSenseConfig</code>
/// <para>This example retrieves the OPNSense firewall configuration and restores it.</para>
/// </example>
/// </summary>
[Cmdlet(VerbsCommon.Get, "OPNSenseConfig")]
[OutputType(typeof(XmlDocument))]
public class GetOPNSenseConfigCmdlet : OPNSenseBaseCmdlet
{
/// <summary>
/// Processes the cmdlet
/// </summary>
protected override void ProcessRecord()
{
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);
}
WriteVerbose("Retrieved OPNSense configuration as XML document");
WriteObject(xmlDoc);
}
catch (Exception ex)
{
HandleException(ex);
}
}
}
}
@@ -23,8 +23,8 @@ namespace PSOPNSenseAPI.Cmdlets
/// <para type="description">The path to the configuration file.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0)]
[ValidateNotNullOrEmpty]
public string Path { get; set; }
[ValidateNotNull]
public FileInfo Path { get; set; }
/// <summary>
/// <para type="description">Suppresses the confirmation prompt.</para>
@@ -39,18 +39,20 @@ namespace PSOPNSenseAPI.Cmdlets
{
try
{
string fullPath = Path.FullName;
// Check if the file exists
if (!File.Exists(Path))
if (!File.Exists(fullPath))
{
WriteError(new ErrorRecord(
new FileNotFoundException($"The file '{Path}' does not exist."),
new FileNotFoundException($"The file '{fullPath}' does not exist."),
"FileNotFound",
ErrorCategory.ObjectNotFound,
Path));
fullPath));
return;
}
if (!Force.IsPresent && !ShouldProcess(Path, "Import configuration"))
if (!Force.IsPresent && !ShouldProcess(fullPath, "Import configuration"))
{
return;
}
@@ -58,12 +60,12 @@ namespace PSOPNSenseAPI.Cmdlets
var configService = new ConfigService(ApiClient, Logger);
// Read the configuration file
var configContent = File.ReadAllBytes(Path);
var configContent = File.ReadAllBytes(fullPath);
var task = Task.Run(async () => await configService.ImportConfigAsync(configContent));
var result = task.GetAwaiter().GetResult();
// Execute the async method synchronously on the main thread
var result = configService.ImportConfigAsync(configContent).GetAwaiter().GetResult();
WriteVerbose($"Imported configuration from {Path}: {result.Status}");
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)
@@ -1,18 +1,30 @@
using System;
using System.IO;
using System.Management.Automation;
using System.Threading.Tasks;
using System.Xml;
using PSOPNSenseAPI.Services;
namespace PSOPNSenseAPI.Cmdlets
{
/// <summary>
/// <para type="synopsis">Restores an OPNSense firewall configuration from a backup.</para>
/// <para type="description">The Restore-OPNSenseConfig cmdlet restores an OPNSense firewall configuration from a backup.</para>
/// <para type="synopsis">Restores an OPNSense firewall configuration.</para>
/// <para type="description">The Restore-OPNSenseConfig cmdlet restores an OPNSense firewall configuration from a backup or an XML document.</para>
/// <example>
/// <para>Example 1: Restore a configuration backup</para>
/// <code>Restore-OPNSenseConfig -Filename "config-backup-20250414-1234.xml"</code>
/// <para>This example restores an OPNSense firewall configuration from a backup file.</para>
/// </example>
/// <example>
/// <para>Example 2: Restore a configuration from an XML document</para>
/// <code>$config = Get-OPNSenseConfig; Restore-OPNSenseConfig -XmlDocument $config</code>
/// <para>This example restores an OPNSense firewall configuration from an XML document.</para>
/// </example>
/// <example>
/// <para>Example 3: Restore a configuration from the pipeline</para>
/// <code>Get-OPNSenseConfig | Restore-OPNSenseConfig</code>
/// <para>This example restores an OPNSense firewall configuration from the pipeline.</para>
/// </example>
/// </summary>
[Cmdlet(VerbsData.Restore, "OPNSenseConfig", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(void))]
@@ -21,10 +33,17 @@ namespace PSOPNSenseAPI.Cmdlets
/// <summary>
/// <para type="description">The filename of the backup to restore.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0)]
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Filename")]
[ValidateNotNullOrEmpty]
public string Filename { get; set; }
/// <summary>
/// <para type="description">The XML document containing the configuration to restore.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "XmlDocument", ValueFromPipeline = true)]
[ValidateNotNull]
public XmlDocument XmlDocument { get; set; }
/// <summary>
/// <para type="description">Suppresses the confirmation prompt.</para>
/// </summary>
@@ -38,17 +57,36 @@ namespace PSOPNSenseAPI.Cmdlets
{
try
{
if (!Force.IsPresent && !ShouldProcess(Filename, "Restore configuration backup"))
var configService = new ConfigService(ApiClient, Logger);
string actionDescription = "Restore configuration";
string targetName = ParameterSetName == "Filename" ? Filename : "from XML document";
if (!Force.IsPresent && !ShouldProcess(targetName, actionDescription))
{
return;
}
var configService = new ConfigService(ApiClient, Logger);
if (ParameterSetName == "Filename")
{
// Restore from backup file - execute synchronously on the main thread
var result = configService.RestoreConfigBackupAsync(Filename).GetAwaiter().GetResult();
WriteVerbose($"Restored configuration from backup: {result.Status}");
}
else
{
// Restore from XML document
byte[] configContent;
using (var memoryStream = new MemoryStream())
{
XmlDocument.Save(memoryStream);
configContent = memoryStream.ToArray();
}
var task = Task.Run(async () => await configService.RestoreConfigBackupAsync(Filename));
var result = task.GetAwaiter().GetResult();
// Execute synchronously on the main thread
var result = configService.ImportConfigAsync(configContent).GetAwaiter().GetResult();
WriteVerbose($"Restored configuration from XML document: {result.Status}");
}
WriteVerbose($"Restored configuration backup: {result.Status}");
WriteWarning("The firewall is restarting. You may need to reconnect after it comes back online.");
}
catch (Exception ex)
@@ -1,5 +1,6 @@
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading;
using System.Threading.Tasks;
using PSOPNSenseAPI.Services;
@@ -20,7 +21,7 @@ namespace PSOPNSenseAPI.Cmdlets
/// <para>This example upgrades the firmware on the OPNSense firewall and waits for the upgrade to complete.</para>
/// </example>
/// </summary>
[Cmdlet(VerbsData.Update, "OPNSenseFirmware", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
[Cmdlet(VerbsLifecycle.Start, "OPNSenseFirmwareUpgrade", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(void))]
public class UpgradeOPNSenseFirmwareCmdlet : OPNSenseBaseCmdlet
{
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFrameworks>netstandard2.0;net472</TargetFrameworks>
<AssemblyName>PSOPNSenseAPI</AssemblyName>
<RootNamespace>PSOPNSenseAPI</RootNamespace>
<Version>2025.04.14.2246</Version>
<Version>2025.04.14.2340</Version>
<Authors>PSOPNSenseAPI Contributors</Authors>
<Company>PSOPNSenseAPI</Company>
<Description>PowerShell module for interacting with the OPNSense API to configure firewalls</Description>
+89 -115
View File
@@ -1,9 +1,9 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'PSOPNSenseAPI.dll'
RootModule = 'lib\PSOPNSenseAPI.dll'
# Version number of this module.
ModuleVersion = '0.5.0'
ModuleVersion = '2025.04.15.1143'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
@@ -33,7 +33,7 @@
# 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'
# 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'
@@ -45,7 +45,7 @@
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
RequiredAssemblies = @('System.Net.Http', 'Newtonsoft.Json', 'System.Net.IPNetwork')
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
@@ -64,127 +64,101 @@
# 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 = @(
# Connection
'Connect-OPNSense',
'Disconnect-OPNSense',
# Firewall Rules
'Get-OPNSenseFirewallRule',
'New-OPNSenseFirewallRule',
'Set-OPNSenseFirewallRule',
'Remove-OPNSenseFirewallRule',
'Enable-OPNSenseFirewallRule',
'Disable-OPNSenseFirewallRule',
'Apply-OPNSenseFirewallChanges',
# Aliases
'Get-OPNSenseAlias',
'New-OPNSenseAlias',
'Set-OPNSenseAlias',
'Remove-OPNSenseAlias',
# Interfaces
'Get-OPNSenseInterface',
'Set-OPNSenseInterface',
'Get-OPNSenseVLAN',
'New-OPNSenseVLAN',
'Remove-OPNSenseVLAN',
'New-OPNSenseSubnetVLANs',
# DNS
'Get-OPNSenseDNSServer',
'Set-OPNSenseDNSServer',
'Get-OPNSenseDNSOverride',
'New-OPNSenseDNSOverride',
'Remove-OPNSenseDNSOverride',
'Get-OPNSenseDNSForwarding',
'Set-OPNSenseDNSForwarding',
'Get-OPNSenseDNSForwardingHost',
'New-OPNSenseDNSForwardingHost',
'Set-OPNSenseDNSForwardingHost',
'Remove-OPNSenseDNSForwardingHost',
'Get-OPNSenseSystemDNS',
'Set-OPNSenseSystemDNS',
# Configuration
'Backup-OPNSenseConfig',
'Restore-OPNSenseConfig',
'Export-OPNSenseConfig',
'Import-OPNSenseConfig',
# Plugins
'Get-OPNSensePlugin',
'Install-OPNSensePlugin',
'Uninstall-OPNSensePlugin',
'Enable-OPNSensePlugin',
'Disable-OPNSensePlugin',
# Users
'Get-OPNSenseUser',
'New-OPNSenseUser',
'Set-OPNSenseUser',
'Remove-OPNSenseUser',
# Firmware
'Get-OPNSenseFirmware',
'Update-OPNSenseFirmware',
'Upgrade-OPNSenseFirmware',
# System
'Restart-OPNSenseFirewall',
# DHCP
'Get-OPNSenseDHCPServer',
'Set-OPNSenseDHCPServer',
'Get-OPNSenseDHCPLease',
'Remove-OPNSenseDHCPLease',
'Get-OPNSenseDHCPStaticMapping',
'New-OPNSenseDHCPStaticMapping',
'Set-OPNSenseDHCPStaticMapping',
'Remove-OPNSenseDHCPStaticMapping',
'Get-OPNSenseDHCPOption',
'New-OPNSenseDHCPOption',
'Set-OPNSenseDHCPOption',
'Remove-OPNSenseDHCPOption',
# Cron
'Get-OPNSenseCronJob',
'New-OPNSenseCronJob',
'Set-OPNSenseCronJob',
'Remove-OPNSenseCronJob',
'Enable-OPNSenseCronJob',
'Disable-OPNSenseCronJob',
# Tailscale
'Get-OPNSenseTailscaleStatus',
'Enable-OPNSenseTailscale',
'Disable-OPNSenseTailscale',
'Connect-OPNSense',
'Connect-OPNSenseTailscale',
'Disconnect-OPNSenseTailscale',
# Network Utilities
'Invoke-OPNSenseNetworkCalculation',
'ConvertTo-OPNSenseNetworkNotation',
# Gateways and Routes
'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',
'New-OPNSenseGateway',
'Set-OPNSenseGateway',
'Remove-OPNSenseGateway',
'Get-OPNSenseRoute',
'New-OPNSenseRoute',
'Set-OPNSenseRoute',
'Remove-OPNSenseRoute',
# Port Forwarding
'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',
'Remove-OPNSensePortForwardingRule'
'Set-OPNSenseRoute',
'Set-OPNSenseSystemDNS',
'Set-OPNSenseUser',
'Set-OPNSenseVLAN',
'Uninstall-OPNSensePlugin',
'Update-OPNSenseFirmware',
'Start-OPNSenseFirmwareUpgrade'
)
# Variables to export from this module
VariablesToExport = '*'
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 = @()
@@ -1,202 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PSOPNSenseAPI.Models;
namespace PSOPNSenseAPI.Services
{
/// <summary>
/// Service for managing port forwarding rules in OPNSense
/// </summary>
public class PortForwardingService : ServiceBase
{
/// <summary>
/// Initializes a new instance of the <see cref="PortForwardingService"/> class
/// </summary>
/// <param name="apiClient">The API client to use for requests</param>
public PortForwardingService(ApiClient apiClient) : base(apiClient)
{
}
/// <summary>
/// Gets all port forwarding rules
/// </summary>
/// <returns>A list of port forwarding rules</returns>
public async Task<List<PortForwardingRule>> GetPortForwardingRulesAsync()
{
var response = await ApiClient.GetAsync("/firewall/nat/getRule");
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
if (jsonResponse["status"]?.ToString() != "ok")
{
throw new Exception($"Failed to get port forwarding rules: {jsonResponse["status_msg"]}");
}
var rules = new List<PortForwardingRule>();
var rulesData = jsonResponse["rows"];
foreach (var rule in rulesData)
{
var portForwardingRule = new PortForwardingRule
{
Uuid = rule["uuid"]?.ToString(),
Enabled = rule["enabled"]?.ToString() == "1",
Description = rule["descr"]?.ToString(),
Interface = rule["interface"]?.ToString(),
Protocol = rule["protocol"]?.ToString(),
Source = rule["source"]?.ToString(),
SourcePort = rule["source_port"]?.ToString(),
Destination = rule["destination"]?.ToString(),
DestinationPort = rule["destination_port"]?.ToString(),
TargetIP = rule["target"]?.ToString(),
TargetPort = rule["target_port"]?.ToString(),
NatReflection = rule["natreflection"]?.ToString(),
FilterRuleAssociation = rule["associated-rule-id"]?.ToString() != "none",
Log = rule["log"]?.ToString() == "1"
};
rules.Add(portForwardingRule);
}
return rules;
}
/// <summary>
/// Gets a port forwarding rule by UUID
/// </summary>
/// <param name="uuid">The UUID of the rule to get</param>
/// <returns>The port forwarding rule</returns>
public async Task<PortForwardingRule> GetPortForwardingRuleAsync(string uuid)
{
var rules = await GetPortForwardingRulesAsync();
return rules.Find(r => r.Uuid == uuid);
}
/// <summary>
/// Creates a new port forwarding rule
/// </summary>
/// <param name="rule">The rule to create</param>
/// <returns>The UUID of the created rule</returns>
public async Task<string> CreatePortForwardingRuleAsync(PortForwardingRule rule)
{
var ruleData = new
{
rule = new
{
enabled = rule.Enabled ? "1" : "0",
descr = rule.Description,
interface = rule.Interface,
protocol = rule.Protocol,
source = rule.Source,
source_port = rule.SourcePort,
destination = rule.Destination,
destination_port = rule.DestinationPort,
target = rule.TargetIP,
target_port = rule.TargetPort,
natreflection = rule.NatReflection,
associated_rule_id = rule.FilterRuleAssociation ? "pass" : "none",
log = rule.Log ? "1" : "0"
}
};
var response = await ApiClient.PostAsync("/firewall/nat/addRule", ruleData);
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
if (jsonResponse["status"]?.ToString() != "ok")
{
throw new Exception($"Failed to create port forwarding rule: {jsonResponse["status_msg"]}");
}
// Apply changes
await ApplyChangesAsync();
return jsonResponse["uuid"]?.ToString();
}
/// <summary>
/// Updates an existing port forwarding rule
/// </summary>
/// <param name="rule">The rule to update</param>
/// <returns>True if successful</returns>
public async Task<bool> UpdatePortForwardingRuleAsync(PortForwardingRule rule)
{
if (string.IsNullOrEmpty(rule.Uuid))
{
throw new ArgumentException("Rule UUID cannot be null or empty", nameof(rule));
}
var ruleData = new
{
rule = new
{
enabled = rule.Enabled ? "1" : "0",
descr = rule.Description,
interface = rule.Interface,
protocol = rule.Protocol,
source = rule.Source,
source_port = rule.SourcePort,
destination = rule.Destination,
destination_port = rule.DestinationPort,
target = rule.TargetIP,
target_port = rule.TargetPort,
natreflection = rule.NatReflection,
associated_rule_id = rule.FilterRuleAssociation ? "pass" : "none",
log = rule.Log ? "1" : "0"
}
};
var response = await ApiClient.PostAsync($"/firewall/nat/setRule/{rule.Uuid}", ruleData);
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
if (jsonResponse["status"]?.ToString() != "ok")
{
throw new Exception($"Failed to update port forwarding rule: {jsonResponse["status_msg"]}");
}
// Apply changes
await ApplyChangesAsync();
return true;
}
/// <summary>
/// Deletes a port forwarding rule
/// </summary>
/// <param name="uuid">The UUID of the rule to delete</param>
/// <returns>True if successful</returns>
public async Task<bool> DeletePortForwardingRuleAsync(string uuid)
{
var response = await ApiClient.PostAsync($"/firewall/nat/delRule/{uuid}", null);
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
if (jsonResponse["status"]?.ToString() != "ok")
{
throw new Exception($"Failed to delete port forwarding rule: {jsonResponse["status_msg"]}");
}
// Apply changes
await ApplyChangesAsync();
return true;
}
/// <summary>
/// Applies the changes to the firewall
/// </summary>
/// <returns>True if successful</returns>
private async Task<bool> ApplyChangesAsync()
{
var response = await ApiClient.PostAsync("/firewall/nat/apply", null);
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
return jsonResponse["status"]?.ToString() == "ok";
}
}
}
-289
View File
@@ -1,289 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PSOPNSenseAPI.Logging;
using PSOPNSenseAPI.Models;
namespace PSOPNSenseAPI.Services
{
public class InterfaceService
{
private readonly OPNSenseApiClient _apiClient;
private readonly ILogger _logger;
public InterfaceService(OPNSenseApiClient apiClient, ILogger logger)
{
_apiClient = apiClient;
_logger = logger;
}
public async Task<InterfaceListResponse> GetInterfacesAsync()
{
_logger.Information("Getting interfaces");
var endpoint = "interfaces/overview/searchInterfaces";
return await _apiClient.GetAsync<InterfaceListResponse>(endpoint);
}
public async Task<InterfaceDetailResponse> GetInterfaceDetailAsync(string interfaceName)
{
_logger.Information($"Getting interface {interfaceName}");
var endpoint = $"interfaces/overview/getInterface/{interfaceName}";
return await _apiClient.GetAsync<InterfaceDetailResponse>(endpoint);
}
public async Task<InterfaceStatisticsResponse> GetInterfaceStatisticsAsync()
{
_logger.Information("Getting interface statistics");
var endpoint = "diagnostics/interface/getInterfaceStatistics";
return await _apiClient.GetAsync<InterfaceStatisticsResponse>(endpoint);
}
public async Task<InterfaceUpdateResponse> UpdateInterfaceAsync(string interfaceName, InterfaceConfig interfaceConfig)
{
_logger.Information($"Updating interface {interfaceName}");
var endpoint = $"interfaces/overview/setInterface/{interfaceName}";
var data = new { interface = interfaceConfig };
return await _apiClient.PostAsync<InterfaceUpdateResponse>(endpoint, data);
}
public async Task<InterfaceRestartResponse> RestartInterfaceAsync(string interfaceName)
{
_logger.Information($"Restarting interface {interfaceName}");
var endpoint = $"interfaces/overview/reconfigure/{interfaceName}";
return await _apiClient.PostAsync<InterfaceRestartResponse>(endpoint);
}
public async Task<VLANListResponse> GetVLANsAsync()
{
_logger.Information("Getting VLANs");
var endpoint = "interfaces/vlan/searchItem";
return await _apiClient.GetAsync<VLANListResponse>(endpoint);
}
public async Task<VLANResponse> GetVLANAsync(string uuid)
{
_logger.Information($"Getting VLAN with UUID {uuid}");
var endpoint = $"interfaces/vlan/getItem/{uuid}";
return await _apiClient.GetAsync<VLANResponse>(endpoint);
}
public async Task<VLANCreateResponse> CreateVLANAsync(VLANConfig vlan)
{
_logger.Information("Creating VLAN");
var endpoint = "interfaces/vlan/addItem";
var data = new { vlan = vlan };
return await _apiClient.PostAsync<VLANCreateResponse>(endpoint, data);
}
public async Task<VLANUpdateResponse> UpdateVLANAsync(string uuid, VLANConfig vlan)
{
_logger.Information($"Updating VLAN with UUID {uuid}");
var endpoint = $"interfaces/vlan/setItem/{uuid}";
var data = new { vlan = vlan };
return await _apiClient.PostAsync<VLANUpdateResponse>(endpoint, data);
}
public async Task<VLANDeleteResponse> DeleteVLANAsync(string uuid)
{
_logger.Information($"Deleting VLAN with UUID {uuid}");
var endpoint = $"interfaces/vlan/delItem/{uuid}";
return await _apiClient.PostAsync<VLANDeleteResponse>(endpoint);
}
}
public class InterfaceListResponse
{
[JsonProperty("rows")]
public List<InterfaceInfo> Interfaces { get; set; }
}
public class InterfaceInfo
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("ipaddr")]
public string IpAddress { get; set; }
[JsonProperty("subnet")]
public string SubnetMask { get; set; }
[JsonProperty("gateway")]
public string Gateway { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
}
public class InterfaceDetailResponse
{
[JsonProperty("interface")]
public InterfaceDetail Interface { get; set; }
}
public class InterfaceDetail
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("ipaddr")]
public string IpAddress { get; set; }
[JsonProperty("subnet")]
public string SubnetMask { get; set; }
[JsonProperty("gateway")]
public string Gateway { get; set; }
[JsonProperty("enable")]
public string Enabled { get; set; }
}
public class InterfaceConfig
{
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("ipaddr")]
public string IpAddress { get; set; }
[JsonProperty("subnet")]
public string SubnetMask { get; set; }
[JsonProperty("gateway")]
public string Gateway { get; set; }
[JsonProperty("enable")]
public string Enabled { get; set; }
}
public class InterfaceUpdateResponse
{
[JsonProperty("result")]
public string Result { get; set; }
}
public class InterfaceRestartResponse
{
[JsonProperty("status")]
public string Status { get; set; }
}
public class InterfaceStatisticsResponse
{
[JsonProperty("statistics")]
public Dictionary<string, InterfaceStatistics> Statistics { get; set; }
}
public class InterfaceStatistics
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("inpkts")]
public string InPackets { get; set; }
[JsonProperty("outpkts")]
public string OutPackets { get; set; }
[JsonProperty("inbytes")]
public string InBytes { get; set; }
[JsonProperty("outbytes")]
public string OutBytes { get; set; }
}
public class VLANListResponse
{
[JsonProperty("rows")]
public List<VLAN> Rows { get; set; }
}
public class VLAN
{
[JsonProperty("uuid")]
public string Uuid { get; set; }
[JsonProperty("if")]
public string Interface { get; set; }
[JsonProperty("tag")]
public string Tag { get; set; }
[JsonProperty("pcp")]
public string Priority { get; set; }
[JsonProperty("descr")]
public string Description { get; set; }
}
public class VLANResponse
{
[JsonProperty("vlan")]
public VLANDetail Vlan { get; set; }
}
public class VLANDetail
{
[JsonProperty("if")]
public string Interface { get; set; }
[JsonProperty("tag")]
public string Tag { get; set; }
[JsonProperty("pcp")]
public string Priority { get; set; }
[JsonProperty("descr")]
public string Description { get; set; }
}
public class VLANConfig
{
[JsonProperty("if")]
public string Interface { get; set; }
[JsonProperty("tag")]
public string Tag { get; set; }
[JsonProperty("pcp")]
public string Priority { get; set; }
[JsonProperty("descr")]
public string Description { get; set; }
}
public class VLANCreateResponse
{
[JsonProperty("uuid")]
public string Uuid { get; set; }
}
public class VLANUpdateResponse
{
[JsonProperty("result")]
public string Result { get; set; }
}
public class VLANDeleteResponse
{
[JsonProperty("result")]
public string Result { get; set; }
}
}
@@ -0,0 +1,184 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using PSOPNSenseAPI.Cmdlets;
using PSOPNSenseAPI.Services;
using PSOPNSenseAPI.Logging;
using System;
using System.Management.Automation;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reflection;
namespace PSOPNSenseAPI.Tests
{
[TestClass]
public class FirmwareCmdletTests
{
private Mock<OPNSenseApiClient> _mockApiClient;
private Mock<ILogger> _mockLogger;
[TestInitialize]
public void Setup()
{
_mockApiClient = new Mock<OPNSenseApiClient>("https://example.com", "key", "secret", false, Mock.Of<ILogger>());
_mockLogger = new Mock<ILogger>();
}
[TestMethod]
public void UpdateOPNSenseFirmwareCmdlet_ProcessRecord_CallsUpdateAsync()
{
// Arrange
_mockApiClient.Setup(c => c.PostAsync<dynamic>("core/firmware/update", It.IsAny<object>()))
.ReturnsAsync(new { status = "ok" });
// Create and configure the cmdlet
var cmdlet = new UpdateOPNSenseFirmwareCmdlet();
// Use reflection to set the protected properties
SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object);
SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object);
// Act
InvokeMethod(cmdlet, "ProcessRecord");
// Assert
_mockApiClient.Verify(c => c.PostAsync<dynamic>("core/firmware/update", It.IsAny<object>()), Times.Once);
}
[TestMethod]
public void StartOPNSenseFirmwareUpgradeCmdlet_ProcessRecord_CallsUpgradeAsync()
{
// Arrange
_mockApiClient.Setup(c => c.PostAsync<dynamic>("core/firmware/upgrade", It.IsAny<object>()))
.ReturnsAsync(new { status = "ok" });
// Create and configure the cmdlet
var cmdlet = new UpgradeOPNSenseFirmwareCmdlet();
// Use reflection to set the protected properties
SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object);
SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object);
// Set Force parameter to skip confirmation
typeof(UpgradeOPNSenseFirmwareCmdlet).GetProperty("Force").SetValue(cmdlet, new SwitchParameter(true));
// Act
InvokeMethod(cmdlet, "ProcessRecord");
// Assert
_mockApiClient.Verify(c => c.PostAsync<dynamic>("core/firmware/upgrade", It.IsAny<object>()), Times.Once);
}
[TestMethod]
public void GetOPNSenseFirmwareCmdlet_ProcessRecord_CallsGetStatusAsync()
{
// Arrange
_mockApiClient.Setup(c => c.GetAsync<dynamic>("core/firmware/status"))
.ReturnsAsync(new { status = "ok" });
// Create and configure the cmdlet
var cmdlet = new GetOPNSenseFirmwareCmdlet();
// Use reflection to set the protected properties
SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object);
SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object);
// Act
InvokeMethod(cmdlet, "ProcessRecord");
// Assert
_mockApiClient.Verify(c => c.GetAsync<dynamic>("core/firmware/status"), Times.Once);
}
[TestMethod]
public void GetOPNSenseFirmwareCmdlet_WithChangelog_CallsGetChangelogAsync()
{
// Arrange
_mockApiClient.Setup(c => c.GetAsync<dynamic>("core/firmware/changelog"))
.ReturnsAsync(new { changelog = "Changes in version X.Y.Z" });
// Create and configure the cmdlet
var cmdlet = new GetOPNSenseFirmwareCmdlet();
// Use reflection to set the protected properties
SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object);
SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object);
// Set Changelog parameter
typeof(GetOPNSenseFirmwareCmdlet).GetProperty("Changelog").SetValue(cmdlet, new SwitchParameter(true));
// Act
InvokeMethod(cmdlet, "ProcessRecord");
// Assert
_mockApiClient.Verify(c => c.GetAsync<dynamic>("core/firmware/changelog"), Times.Once);
}
[TestMethod]
public void GetOPNSenseFirmwareCmdlet_WithAudit_CallsGetAuditAsync()
{
// Arrange
_mockApiClient.Setup(c => c.GetAsync<dynamic>("core/firmware/audit"))
.ReturnsAsync(new { audit = new[] { new { name = "package1", version = "1.0.0" } } });
// Create and configure the cmdlet
var cmdlet = new GetOPNSenseFirmwareCmdlet();
// Use reflection to set the protected properties
SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object);
SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object);
// Set Audit parameter
typeof(GetOPNSenseFirmwareCmdlet).GetProperty("Audit").SetValue(cmdlet, new SwitchParameter(true));
// Act
InvokeMethod(cmdlet, "ProcessRecord");
// Assert
_mockApiClient.Verify(c => c.GetAsync<dynamic>("core/firmware/audit"), Times.Once);
}
private void SetProtectedProperty(object obj, string propertyName, object value)
{
var propertyInfo = obj.GetType().BaseType.GetProperty(
propertyName,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (propertyInfo == null)
{
throw new ArgumentException($"Property {propertyName} not found on {obj.GetType().FullName}");
}
var backingField = obj.GetType().BaseType.GetField(
$"<{propertyName}>k__BackingField",
BindingFlags.Instance | BindingFlags.NonPublic);
if (backingField != null)
{
backingField.SetValue(obj, value);
}
else if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(obj, value);
}
else
{
throw new ArgumentException($"Cannot set property {propertyName} on {obj.GetType().FullName}");
}
}
private void InvokeMethod(object obj, string methodName)
{
var methodInfo = obj.GetType().GetMethod(
methodName,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (methodInfo == null)
{
throw new ArgumentException($"Method {methodName} not found on {obj.GetType().FullName}");
}
methodInfo.Invoke(obj, null);
}
}
}
@@ -0,0 +1,123 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using PSOPNSenseAPI.Cmdlets;
using PSOPNSenseAPI.Services;
using PSOPNSenseAPI.Logging;
using System.Threading.Tasks;
using System.Management.Automation;
using System.Collections.ObjectModel;
namespace PSOPNSenseAPI.Tests
{
[TestClass]
public class FirmwareServiceTests
{
private Mock<OPNSenseApiClient> _mockApiClient;
private Mock<ILogger> _mockLogger;
private FirmwareService _firmwareService;
[TestInitialize]
public void Setup()
{
_mockApiClient = new Mock<OPNSenseApiClient>("https://example.com", "key", "secret", false, Mock.Of<ILogger>());
_mockLogger = new Mock<ILogger>();
_firmwareService = new FirmwareService(_mockApiClient.Object, _mockLogger.Object);
}
[TestMethod]
public async Task GetStatusAsync_ReturnsStatus()
{
// Arrange
var expectedResponse = new { status = "ok" };
_mockApiClient.Setup(c => c.GetAsync<dynamic>("core/firmware/status"))
.ReturnsAsync(expectedResponse);
// Act
var result = await _firmwareService.GetStatusAsync();
// Assert
Assert.AreEqual("ok", result.Status);
_mockApiClient.Verify(c => c.GetAsync<dynamic>("core/firmware/status"), Times.Once);
}
[TestMethod]
public async Task UpdateAsync_CallsCorrectEndpoint()
{
// Arrange
var expectedResponse = new { status = "ok" };
_mockApiClient.Setup(c => c.PostAsync<dynamic>("core/firmware/update", It.IsAny<object>()))
.ReturnsAsync(expectedResponse);
// Act
var result = await _firmwareService.UpdateAsync();
// Assert
Assert.AreEqual("ok", result.Status);
_mockApiClient.Verify(c => c.PostAsync<dynamic>("core/firmware/update", It.IsAny<object>()), Times.Once);
}
[TestMethod]
public async Task UpgradeAsync_CallsCorrectEndpoint()
{
// Arrange
var expectedResponse = new { status = "ok" };
_mockApiClient.Setup(c => c.PostAsync<dynamic>("core/firmware/upgrade", It.IsAny<object>()))
.ReturnsAsync(expectedResponse);
// Act
var result = await _firmwareService.UpgradeAsync();
// Assert
Assert.AreEqual("ok", result.Status);
_mockApiClient.Verify(c => c.PostAsync<dynamic>("core/firmware/upgrade", It.IsAny<object>()), Times.Once);
}
[TestMethod]
public async Task GetChangelogAsync_ReturnsChangelog()
{
// Arrange
var expectedResponse = new { changelog = "Changes in version X.Y.Z" };
_mockApiClient.Setup(c => c.GetAsync<dynamic>("core/firmware/changelog"))
.ReturnsAsync(expectedResponse);
// Act
var result = await _firmwareService.GetChangelogAsync();
// Assert
Assert.AreEqual("Changes in version X.Y.Z", result.Changelog);
_mockApiClient.Verify(c => c.GetAsync<dynamic>("core/firmware/changelog"), Times.Once);
}
[TestMethod]
public async Task GetHealthAsync_ReturnsHealth()
{
// Arrange
var expectedResponse = new { health = "healthy" };
_mockApiClient.Setup(c => c.GetAsync<dynamic>("core/firmware/health"))
.ReturnsAsync(expectedResponse);
// Act
var result = await _firmwareService.GetHealthAsync();
// Assert
Assert.AreEqual("healthy", result.Health);
_mockApiClient.Verify(c => c.GetAsync<dynamic>("core/firmware/health"), Times.Once);
}
[TestMethod]
public async Task GetAuditAsync_ReturnsAudit()
{
// Arrange
var expectedResponse = new { audit = new[] { new { name = "package1", version = "1.0.0" } } };
_mockApiClient.Setup(c => c.GetAsync<dynamic>("core/firmware/audit"))
.ReturnsAsync(expectedResponse);
// Act
var result = await _firmwareService.GetAuditAsync();
// Assert
Assert.IsNotNull(result.Audit);
_mockApiClient.Verify(c => c.GetAsync<dynamic>("core/firmware/audit"), Times.Once);
}
}
}
@@ -11,9 +11,9 @@ namespace PSOPNSenseAPI.Tests
[TestClass]
public class InterfaceServiceTests
{
private Mock<IOPNSenseApiClient> _mockApiClient;
private Mock<ILogger> _mockLogger;
private InterfaceService _interfaceService;
private Mock<IOPNSenseApiClient>? _mockApiClient;
private Mock<ILogger>? _mockLogger;
private InterfaceService? _interfaceService;
[TestInitialize]
public void Setup()
@@ -43,11 +43,11 @@ namespace PSOPNSenseAPI.Tests
}
};
_mockApiClient.Setup(x => x.GetAsync<InterfaceListResponse>("interfaces/overview/searchInterfaces"))
_mockApiClient!.Setup(x => x.GetAsync<InterfaceListResponse>("interfaces/overview/searchInterfaces"))
.ReturnsAsync(response);
// Act
var result = await _interfaceService.GetInterfacesAsync();
var result = await _interfaceService!.GetInterfacesAsync();
// Assert
Assert.IsNotNull(result);
@@ -78,11 +78,11 @@ namespace PSOPNSenseAPI.Tests
}
};
_mockApiClient.Setup(x => x.GetAsync<InterfaceDetailResponse>($"interfaces/overview/getInterface/{interfaceName}"))
_mockApiClient!.Setup(x => x.GetAsync<InterfaceDetailResponse>($"interfaces/overview/getInterface/{interfaceName}"))
.ReturnsAsync(response);
// Act
var result = await _interfaceService.GetInterfaceDetailAsync(interfaceName);
var result = await _interfaceService!.GetInterfaceDetailAsync(interfaceName);
// Assert
Assert.IsNotNull(result);
@@ -114,11 +114,11 @@ namespace PSOPNSenseAPI.Tests
Result = "saved"
};
_mockApiClient.Setup(x => x.PostAsync<InterfaceUpdateResponse>($"interfaces/overview/setInterface/{interfaceName}", It.IsAny<object>()))
_mockApiClient!.Setup(x => x.PostAsync<InterfaceUpdateResponse>($"interfaces/overview/setInterface/{interfaceName}", It.IsAny<object>()))
.ReturnsAsync(response);
// Act
var result = await _interfaceService.UpdateInterfaceAsync(interfaceName, interfaceConfig);
var result = await _interfaceService!.UpdateInterfaceAsync(interfaceName, interfaceConfig);
// Assert
Assert.IsNotNull(result);