9 Commits

16 changed files with 709 additions and 429 deletions
+5 -1
View File
@@ -5,7 +5,11 @@ 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.0739] - 2025-04-15
### Changed
- Renamed `Upgrade-OPNSenseFirmware` to `Start-OPNSenseFirmwareUpgrade` to resolve cmdlet name conflict
- Added unit tests for firmware management cmdlets
## [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.
+3
View File
@@ -135,6 +135,9 @@ 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
+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.
+15 -5
View File
@@ -113,14 +113,24 @@ The solution includes unit tests that can be run from Visual Studio using the Te
.\build\test.ps1
```
## Manual Release Process
## Release Process
The project uses a manual release process. To create a new release:
The project includes a script to automate the release process. To create a new release:
1. Update the version in the project file and module manifest
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. Create a zip file of the module
4. Create a GitHub release with the zip file attached
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
+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
+95 -93
View File
@@ -1,6 +1,6 @@
@{
RootModule = 'PSOPNSenseAPI.psm1'
ModuleVersion = '2025.04.14.2340'
ModuleVersion = '2025.04.15.0739'
GUID = '1f0e4b77-cc7c-4a1e-b45a-d7c51a3c562e'
Author = 'PSOPNSenseAPI Contributors'
CompanyName = 'PSOPNSenseAPI'
@@ -8,104 +8,106 @@
Description = 'PowerShell module for interacting with the OPNSense API to configure firewalls'
PowerShellVersion = '5.1'
CompatiblePSEditions = @('Desktop', 'Core')
DotNetFrameworkVersion = '4.7.2'
#DotNetFrameworkVersion = '4.7.2'
CLRVersion = '4.0.0'
FunctionsToExport = @()
CmdletsToExport = @(
'ApplyOPNSenseFirewallChanges',
'BackupOPNSenseConfig',
'ConnectOPNSense',
'ConnectOPNSenseTailscale',
'ConvertToOPNSenseNetworkNotation',
'DisableOPNSenseCronJob',
'DisableOPNSenseFirewallRule',
'DisableOPNSensePlugin',
'DisableOPNSenseTailscale',
'DisconnectOPNSense',
'DisconnectOPNSenseTailscale',
'EnableOPNSenseCronJob',
'EnableOPNSenseFirewallRule',
'EnableOPNSensePlugin',
'EnableOPNSenseTailscale',
'ExportOPNSenseConfig',
'GetOPNSenseAlias',
'GetOPNSenseConfigBackup',
'GetOPNSenseConnection',
'GetOPNSenseCronJob',
'GetOPNSenseDHCPLease',
'GetOPNSenseDHCPOption',
'GetOPNSenseDHCPServer',
'GetOPNSenseDHCPStaticMapping',
'GetOPNSenseDNSForwarding',
'GetOPNSenseDNSForwardingHost',
'GetOPNSenseDNSOverride',
'GetOPNSenseDNSServer',
'GetOPNSenseFirewallRule',
'GetOPNSenseFirmware',
'GetOPNSenseGateway',
'GetOPNSenseInterface',
'GetOPNSenseInterfaceStatistics',
'GetOPNSensePlugin',
'GetOPNSensePortForwardingRule',
'GetOPNSenseRoute',
'GetOPNSenseSystemDNS',
'GetOPNSenseTailscaleStatus',
'GetOPNSenseUser',
'GetOPNSenseVLAN',
'ImportOPNSenseConfig',
'InstallOPNSensePlugin',
'InvokeOPNSenseNetworkCalculation',
'NewOPNSenseAlias',
'NewOPNSenseCronJob',
'NewOPNSenseDHCPOption',
'NewOPNSenseDHCPStaticMapping',
'NewOPNSenseDNSForwardingHost',
'NewOPNSenseDNSOverride',
'NewOPNSenseFirewallRule',
'NewOPNSenseGateway',
'NewOPNSensePortForwardingRule',
'NewOPNSenseRoute',
'NewOPNSenseSubnetVLANs',
'NewOPNSenseUser',
'NewOPNSenseVLAN',
'RemoveOPNSenseAlias',
'RemoveOPNSenseCronJob',
'RemoveOPNSenseDHCPLease',
'RemoveOPNSenseDHCPOption',
'RemoveOPNSenseDHCPStaticMapping',
'RemoveOPNSenseDNSForwardingHost',
'RemoveOPNSenseDNSOverride',
'RemoveOPNSenseFirewallRule',
'RemoveOPNSenseGateway',
'RemoveOPNSensePortForwardingRule',
'RemoveOPNSenseRoute',
'RemoveOPNSenseUser',
'RemoveOPNSenseVLAN',
'RestartOPNSenseFirewall',
'RestartOPNSenseInterface',
'RestoreOPNSenseConfig',
'SetOPNSenseAlias',
'SetOPNSenseCronJob',
'SetOPNSenseDHCPOption',
'SetOPNSenseDHCPServer',
'SetOPNSenseDHCPStaticMapping',
'SetOPNSenseDNSForwarding',
'SetOPNSenseDNSForwardingHost',
'SetOPNSenseDNSServer',
'SetOPNSenseFirewallRule',
'SetOPNSenseGateway',
'SetOPNSenseInterface',
'SetOPNSensePortForwardingRule',
'SetOPNSenseRoute',
'SetOPNSenseSystemDNS',
'SetOPNSenseUser',
'SetOPNSenseVLAN',
'UninstallOPNSensePlugin',
'UpdateOPNSenseFirmware',
'UpgradeOPNSenseFirmware'
'Apply-OPNSenseFirewallChanges',
'Backup-OPNSenseConfig',
'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-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'
)
VariablesToExport = @()
AliasesToExport = @()
RequiredAssemblies = @('lib\System.Net.IPNetwork.dll', 'lib\Newtonsoft.Json.dll')
NestedModules = @('lib\PSOPNSenseAPI.dll')
PrivateData = @{
PSData = @{
Tags = @('PowerShell', 'OPNSense', 'Firewall', 'API')
+2 -25
View File
@@ -1,25 +1,2 @@
# 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 {
$dllBytes = [System.IO.File]::ReadAllBytes($dll.FullName)
$Null = [System.Reflection.Assembly]::Load($dllBytes)
Write-Verbose "Attempting to load assembly: $($dll.FullName)"
}
catch {
Write-Warning "Failed to load assembly $($dll.Name): $_"
}
}
# Export all cmdlets
$cmdlets = [System.AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.FullName -like "*PSOPNSenseAPI*" } |
ForEach-Object { $_.GetTypes() } |
Where-Object { $_.IsPublic -and $_.IsClass -and $_.Name -like "*Cmdlet" } |
ForEach-Object { $_.Name }
Export-ModuleMember -Cmdlet $cmdlets
# This file is intentionally empty.
# The module uses NestedModules in the PSD1 file to load the assemblies.
Binary file not shown.
+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,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
{
+92 -92
View File
@@ -3,7 +3,7 @@
RootModule = 'PSOPNSenseAPI.psm1'
# Version number of this module.
ModuleVersion = '2025.04.14.2340'
ModuleVersion = '2025.04.15.0739'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
@@ -64,97 +64,97 @@
# 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 = @(
'ApplyOPNSenseFirewallChanges',
'BackupOPNSenseConfig',
'ConnectOPNSense',
'ConnectOPNSenseTailscale',
'ConvertToOPNSenseNetworkNotation',
'DisableOPNSenseCronJob',
'DisableOPNSenseFirewallRule',
'DisableOPNSensePlugin',
'DisableOPNSenseTailscale',
'DisconnectOPNSense',
'DisconnectOPNSenseTailscale',
'EnableOPNSenseCronJob',
'EnableOPNSenseFirewallRule',
'EnableOPNSensePlugin',
'EnableOPNSenseTailscale',
'ExportOPNSenseConfig',
'GetOPNSenseAlias',
'GetOPNSenseConfigBackup',
'GetOPNSenseConnection',
'GetOPNSenseCronJob',
'GetOPNSenseDHCPLease',
'GetOPNSenseDHCPOption',
'GetOPNSenseDHCPServer',
'GetOPNSenseDHCPStaticMapping',
'GetOPNSenseDNSForwarding',
'GetOPNSenseDNSForwardingHost',
'GetOPNSenseDNSOverride',
'GetOPNSenseDNSServer',
'GetOPNSenseFirewallRule',
'GetOPNSenseFirmware',
'GetOPNSenseGateway',
'GetOPNSenseInterface',
'GetOPNSenseInterfaceStatistics',
'GetOPNSensePlugin',
'GetOPNSensePortForwardingRule',
'GetOPNSenseRoute',
'GetOPNSenseSystemDNS',
'GetOPNSenseTailscaleStatus',
'GetOPNSenseUser',
'GetOPNSenseVLAN',
'ImportOPNSenseConfig',
'InstallOPNSensePlugin',
'InvokeOPNSenseNetworkCalculation',
'NewOPNSenseAlias',
'NewOPNSenseCronJob',
'NewOPNSenseDHCPOption',
'NewOPNSenseDHCPStaticMapping',
'NewOPNSenseDNSForwardingHost',
'NewOPNSenseDNSOverride',
'NewOPNSenseFirewallRule',
'NewOPNSenseGateway',
'NewOPNSensePortForwardingRule',
'NewOPNSenseRoute',
'NewOPNSenseSubnetVLANs',
'NewOPNSenseUser',
'NewOPNSenseVLAN',
'RemoveOPNSenseAlias',
'RemoveOPNSenseCronJob',
'RemoveOPNSenseDHCPLease',
'RemoveOPNSenseDHCPOption',
'RemoveOPNSenseDHCPStaticMapping',
'RemoveOPNSenseDNSForwardingHost',
'RemoveOPNSenseDNSOverride',
'RemoveOPNSenseFirewallRule',
'RemoveOPNSenseGateway',
'RemoveOPNSensePortForwardingRule',
'RemoveOPNSenseRoute',
'RemoveOPNSenseUser',
'RemoveOPNSenseVLAN',
'RestartOPNSenseFirewall',
'RestartOPNSenseInterface',
'RestoreOPNSenseConfig',
'SetOPNSenseAlias',
'SetOPNSenseCronJob',
'SetOPNSenseDHCPOption',
'SetOPNSenseDHCPServer',
'SetOPNSenseDHCPStaticMapping',
'SetOPNSenseDNSForwarding',
'SetOPNSenseDNSForwardingHost',
'SetOPNSenseDNSServer',
'SetOPNSenseFirewallRule',
'SetOPNSenseGateway',
'SetOPNSenseInterface',
'SetOPNSensePortForwardingRule',
'SetOPNSenseRoute',
'SetOPNSenseSystemDNS',
'SetOPNSenseUser',
'SetOPNSenseVLAN',
'UninstallOPNSensePlugin',
'UpdateOPNSenseFirmware',
'UpgradeOPNSenseFirmware'
'Apply-OPNSenseFirewallChanges',
'Backup-OPNSenseConfig',
'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-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
+13
View File
@@ -0,0 +1,13 @@
# Load the main assembly directly
$mainDllPath = Join-Path $PSScriptRoot "lib\PSOPNSenseAPI.dll"
Write-Verbose "Loading assembly from $mainDllPath"
try {
Add-Type -Path $mainDllPath
Write-Verbose "Successfully loaded assembly from $mainDllPath"
} catch {
$errorMessage = $_.Exception.Message
Write-Error "Failed to load assembly from $($mainDllPath): $($errorMessage)"
throw
}
@@ -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);
}
}
}