Files
PSProxmoxVE/REVIEW_REPORT.md
T
Clint Branham a72642d203 feat: add firewall, backup, and SDN IPAM/DNS/controller cmdlets
Add 35 new cmdlets bringing the total to 118:
- Firewall (21): rules, groups, aliases, IP sets, options at
  cluster/node/VM/container levels
- Backup (5): ad-hoc vzdump and scheduled backup job CRUD
- SDN IPAM/DNS/Controller (9): plugin management for SDN subsystem

Also includes:
- Fix: Remove-PveRole now has ConfirmImpact.High
- Fix: URL-encode snapshot names in API paths
- Refactor: extract auth header strings to constants in PveHttpClient
- Add PSGallery version badge to README
- Full test coverage: 11 JSON fixtures, xUnit model tests, Pester
  unit tests, and integration tests for firewall/backup/OVA import
- Updated manifest, format file, CHANGELOG, README, API coverage docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 07:51:49 -05:00

780 lines
37 KiB
Markdown

# PSProxmoxVE Module Review Report
```
Scan date: 2026-03-21
Prior report date: 2026-03-19
```
**Reviewer:** Claude Code (Opus 4.6)
**Module Version:** 0.1.0-preview
**Repository:** PSProxmoxVE (C# binary PowerShell module)
---
## Executive Summary
**Delta since last scan: 22 fixed | 2 new findings | 0 regressed | 4 still open**
1. **Significant progress since prior scan** — 22 of 28 findings from the initial report have been resolved, including all Critical items.
2. **83 cmdlets** (up from 66) covering VMs, containers (with snapshots and migration), storage, networking, SDN (zones, VNets, subnets), users/roles/permissions, templates, cloud-init, OVA/disk import, and tasks.
3. **All prior Critical items resolved**: publish workflow added, README updated with `Install-Module`, ReleaseNotes in manifest.
4. **Community files complete**: CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, .gitattributes, issue/PR templates all added.
5. **Code quality improvements**: HelpMessage on all parameters, WriteVerbose throughout, ValidateRange on VmId, ConfirmImpact.High on Stop/Reset-PveVm, Reset verb constant fixed.
6. **MAML help file** present (PSProxmoxVE.dll-Help.xml) with 75 cmdlet documentation files in docs/cmdlets/.
7. **Remaining gaps**: No IconUri in manifest, Remove-PveRole missing ConfirmImpact.High, firewall/backup/pool cmdlets not yet implemented.
8. **Excellent test coverage**: 100% Pester unit tests, 84% integration test coverage, xUnit model tests with PVE 8 & 9 fixtures.
9. **Security posture is clean**: No hardcoded secrets, HTTPS enforced, SkipCertificateCheck emits warning, all dependencies pinned.
10. **Module is near PSGallery-ready** — no blocking issues remain.
---
## Phase 1 — Repository Inventory & Structure
### Directory Tree (depth ≤ 4)
```
PSProxmoxVE/
├── PSProxmoxVE.sln
├── README.md
├── LICENSE (MIT)
├── CHANGELOG.md
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
├── SECURITY.md
├── .editorconfig
├── .gitignore
├── .gitattributes
├── CLAUDE.md
├── REVIEW_REPORT.md
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── feature_request.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── build.yml
│ ├── unit-tests.yml
│ ├── integration-tests.yml
│ └── publish.yml
├── docs/
│ ├── PVE_API_COVERAGE.md
│ └── cmdlets/ (75 cmdlet documentation files)
├── src/
│ ├── PSProxmoxVE/
│ │ ├── PSProxmoxVE.csproj
│ │ ├── PSProxmoxVE.psd1
│ │ ├── PSProxmoxVE.format.ps1xml
│ │ ├── PSProxmoxVE.dll-Help.xml
│ │ ├── ModuleState.cs
│ │ └── Cmdlets/
│ │ ├── PveCmdletBase.cs
│ │ ├── CloudInit/ (3 cmdlets)
│ │ ├── Connection/ (3 cmdlets)
│ │ ├── Containers/ (14 cmdlets)
│ │ ├── Network/ (14 cmdlets)
│ │ ├── Nodes/ (2 cmdlets)
│ │ ├── Snapshots/ (4 cmdlets)
│ │ ├── Storage/ (6 cmdlets)
│ │ ├── Tasks/ (2 cmdlets)
│ │ ├── Templates/ (4 cmdlets)
│ │ ├── Users/ (12 cmdlets)
│ │ └── Vms/ (19 cmdlets)
│ └── PSProxmoxVE.Core/
│ ├── PSProxmoxVE.Core.csproj
│ ├── Authentication/ (PveAuthenticator, PveAuthMode, PveSession, PveVersion)
│ ├── Client/ (PveHttpClient)
│ ├── Exceptions/ (7 exception types)
│ ├── Models/ (25 model classes)
│ └── Services/ (11 service classes)
├── tests/
│ ├── PSProxmoxVE.Core.Tests/ (xUnit)
│ │ ├── Authentication/ (3 test files)
│ │ ├── Fixtures/ (23 JSON fixtures)
│ │ ├── Models/ (10 test files)
│ │ └── TestHelper.cs
│ ├── PSProxmoxVE.Tests/ (Pester 5)
│ │ ├── _TestHelper.ps1
│ │ ├── CloudInit/
│ │ ├── Connection/ (3 test files)
│ │ ├── Containers/ (4 test files)
│ │ ├── Network/ (3 test files)
│ │ ├── Nodes/ (1 test file)
│ │ ├── Snapshots/ (1 test file)
│ │ ├── Storage/ (3 test files)
│ │ ├── Tasks/ (1 test file)
│ │ ├── Templates/ (1 test file)
│ │ ├── Users/ (3 test files)
│ │ ├── Vms/ (9 test files)
│ │ └── Integration/ (1 comprehensive test file)
│ └── infrastructure/ (Terraform, Dockerfile, scripts for CI)
└── tools/
```
### Project Layout
| Item | Value |
|---|---|
| Solution | `PSProxmoxVE.sln` |
| Main project | `src/PSProxmoxVE/PSProxmoxVE.csproj` |
| Core library | `src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj` |
| Test project (xUnit) | `tests/PSProxmoxVE.Core.Tests/PSProxmoxVE.Core.Tests.csproj` |
| Test project (Pester) | `tests/PSProxmoxVE.Tests/` |
| Target Frameworks | `netstandard2.0`, `net9.0`, `net48` |
| Language Version | C# 10.0 |
| Nullable | Enabled |
| Assembly Names | `PSProxmoxVE`, `PSProxmoxVE.Core` |
### CI/CD Workflows
| Workflow | Purpose |
|---|---|
| `build.yml` | Build + xUnit tests on net48 (Windows) and net9.0 (Windows + Ubuntu) |
| `unit-tests.yml` | Build + Pester tests across PS 5.1/7.5 on Windows/Ubuntu/macOS |
| `integration-tests.yml` | Full integration tests against nested PVE 8 + PVE 9 instances |
| `publish.yml` | Tag-triggered publish to PSGallery + GitHub Release creation |
### Inventory Checklist
| Item | Present | Prior Status |
|---|---|---|
| Solution file (`.sln`) | Yes | Open |
| Module manifest (`.psd1`) | Yes | Open |
| Format definitions (`.format.ps1xml`) | Yes | Open |
| MAML help (`.dll-Help.xml`) | Yes | New |
| `.editorconfig` | Yes | Open |
| `.gitignore` | Yes | Open |
| `.gitattributes` | Yes | Fixed |
| `LICENSE` (MIT) | Yes | Open |
| `CHANGELOG.md` | Yes | Open |
| `README.md` | Yes | Open |
| `CONTRIBUTING.md` | Yes | Fixed |
| `CODE_OF_CONDUCT.md` | Yes | Fixed |
| `SECURITY.md` | Yes | Fixed |
| `.github/ISSUE_TEMPLATE/` | Yes | Fixed |
| `.github/pull_request_template.md` | Yes | Fixed |
| PSGallery publish workflow | Yes | Fixed |
| `docs/PVE_API_COVERAGE.md` | Yes | New |
| `docs/cmdlets/` (75 files) | Yes | New |
### What's Missing
- [ ] `IconUri` in manifest PSData (cosmetic)
---
## Phase 2 — PVE API Coverage Audit
> **Note:** Coverage assessment based on source code analysis and the project's own `docs/PVE_API_COVERAGE.md`.
### Implemented Cmdlets by Category (83 total)
#### Connection (3 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Connect-PveServer` | `/access/ticket` | POST | Open |
| `Disconnect-PveServer` | `/access/ticket` | DELETE | Open |
| `Test-PveConnection` | `/version` | GET | Open |
#### Nodes (2 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveNode` | `/nodes` | GET | Open |
| `Get-PveNodeStatus` | `/nodes/{node}/status` | GET | Open |
#### VMs / QEMU (19 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveVm` | `/nodes/{node}/qemu` | GET | Open |
| `New-PveVm` | `/nodes/{node}/qemu` | POST | Open |
| `Remove-PveVm` | `/nodes/{node}/qemu/{vmid}` | DELETE | Open |
| `Start-PveVm` | `/nodes/{node}/qemu/{vmid}/status/start` | POST | Open |
| `Stop-PveVm` | `/nodes/{node}/qemu/{vmid}/status/stop` | POST | Open |
| `Restart-PveVm` | `/nodes/{node}/qemu/{vmid}/status/reboot` | POST | Open |
| `Suspend-PveVm` | `/nodes/{node}/qemu/{vmid}/status/suspend` | POST | Open |
| `Resume-PveVm` | `/nodes/{node}/qemu/{vmid}/status/resume` | POST | Open |
| `Reset-PveVm` | `/nodes/{node}/qemu/{vmid}/status/reset` | POST | Open |
| `Copy-PveVm` | `/nodes/{node}/qemu/{vmid}/clone` | POST | Open |
| `Move-PveVm` | `/nodes/{node}/qemu/{vmid}/migrate` | POST | Open |
| `Get-PveVmConfig` | `/nodes/{node}/qemu/{vmid}/config` | GET | Open |
| `Set-PveVmConfig` | `/nodes/{node}/qemu/{vmid}/config` | PUT | Open |
| `Resize-PveVmDisk` | `/nodes/{node}/qemu/{vmid}/resize` | PUT | Open |
| `Import-PveVmDisk` | `/nodes/{node}/storage/{storage}/upload` | POST | New |
| `Import-PveOva` | Multiple (upload + config + import) | POST | New |
| `Test-PveVmGuestAgent` | `/nodes/{node}/qemu/{vmid}/agent/ping` | POST | Open |
| `Get-PveVmGuestNetwork` | `/nodes/{node}/qemu/{vmid}/agent/network-get-interfaces` | GET | Open |
| `Invoke-PveVmGuestExec` | `/nodes/{node}/qemu/{vmid}/agent/exec` | POST | Open |
#### Containers / LXC (10 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveContainer` | `/nodes/{node}/lxc` | GET | Open |
| `New-PveContainer` | `/nodes/{node}/lxc` | POST | Open |
| `Remove-PveContainer` | `/nodes/{node}/lxc/{vmid}` | DELETE | Open |
| `Start-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/start` | POST | Open |
| `Stop-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/stop` | POST | Open |
| `Restart-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/shutdown` | POST | Open |
| `Copy-PveContainer` | `/nodes/{node}/lxc/{vmid}/clone` | POST | Open |
| `Move-PveContainer` | `/nodes/{node}/lxc/{vmid}/migrate` | POST | New |
| `Get-PveContainerConfig` | `/nodes/{node}/lxc/{vmid}/config` | GET | Open |
| `Set-PveContainerConfig` | `/nodes/{node}/lxc/{vmid}/config` | PUT | Open |
#### Container Snapshots (4 cmdlets — NEW)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot` | GET | New |
| `New-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot` | POST | New |
| `Remove-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot/{name}` | DELETE | New |
| `Restore-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot/{name}/rollback` | POST | New |
#### Storage (6 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveStorage` | `/storage` or `/nodes/{node}/storage` | GET | Open |
| `Get-PveStorageContent` | `/nodes/{node}/storage/{storage}/content` | GET | Open |
| `Send-PveFile` | `/nodes/{node}/storage/{storage}/upload` | POST | Open |
| `Invoke-PveStorageDownload` | `/nodes/{node}/storage/{storage}/download-url` | POST | Open |
| `New-PveStorage` | `/storage` | POST | Open |
| `Remove-PveStorage` | `/storage/{storage}` | DELETE | Open |
#### VM Snapshots (4 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot` | GET | Open |
| `New-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot` | POST | Open |
| `Remove-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot/{name}` | DELETE | Open |
| `Restore-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot/{name}/rollback` | POST | Open |
#### Networking (5 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveNetwork` | `/nodes/{node}/network` | GET | Open |
| `New-PveNetwork` | `/nodes/{node}/network` | POST | Open |
| `Set-PveNetwork` | `/nodes/{node}/network/{iface}` | PUT | Open |
| `Remove-PveNetwork` | `/nodes/{node}/network/{iface}` | DELETE | Open |
| `Invoke-PveNetworkApply` | `/nodes/{node}/network` | PUT | Open |
#### SDN (9 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveSdnZone` | `/cluster/sdn/zones` | GET | Open |
| `New-PveSdnZone` | `/cluster/sdn/zones` | POST | Open |
| `Remove-PveSdnZone` | `/cluster/sdn/zones/{zone}` | DELETE | Open |
| `Get-PveSdnVnet` | `/cluster/sdn/vnets` | GET | Open |
| `New-PveSdnVnet` | `/cluster/sdn/vnets` | POST | Open |
| `Remove-PveSdnVnet` | `/cluster/sdn/vnets/{vnet}` | DELETE | Open |
| `Get-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets` | GET | New |
| `New-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets` | POST | New |
| `Remove-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets/{subnet}` | DELETE | New |
#### Users / ACLs / Tokens (12 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveUser` | `/access/users` | GET | Open |
| `New-PveUser` | `/access/users` | POST | Open |
| `Remove-PveUser` | `/access/users/{userid}` | DELETE | Open |
| `Set-PveUser` | `/access/users/{userid}` | PUT | Open |
| `Get-PveRole` | `/access/roles` | GET | Open |
| `New-PveRole` | `/access/roles` | POST | Open |
| `Remove-PveRole` | `/access/roles/{roleid}` | DELETE | Open |
| `Get-PvePermission` | `/access/acl` | GET | Open |
| `Set-PvePermission` | `/access/acl` | PUT | Open |
| `Get-PveApiToken` | `/access/users/{userid}/token` | GET | Open |
| `New-PveApiToken` | `/access/users/{userid}/token/{tokenid}` | POST | Open |
| `Remove-PveApiToken` | `/access/users/{userid}/token/{tokenid}` | DELETE | Open |
#### Templates (4 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveTemplate` | `/nodes/{node}/qemu` (filtered) | GET | Open |
| `New-PveTemplate` | `/nodes/{node}/qemu/{vmid}/template` | POST | Open |
| `Remove-PveTemplate` | `/nodes/{node}/qemu/{vmid}` | DELETE | Open |
| `New-PveVmFromTemplate` | `/nodes/{node}/qemu/{vmid}/clone` | POST | Open |
#### Cloud-Init (3 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveCloudInitConfig` | `/nodes/{node}/qemu/{vmid}/config` | GET | Open |
| `Set-PveCloudInitConfig` | `/nodes/{node}/qemu/{vmid}/config` | PUT | Open |
| `Invoke-PveCloudInitRegenerate` | `/nodes/{node}/qemu/{vmid}/cloudinit` | PUT | Open |
#### Tasks (2 cmdlets)
| Cmdlet | API Endpoint | Method | Prior Status |
|---|---|---|---|
| `Get-PveTask` | `/nodes/{node}/tasks/{upid}/status` | GET | Open |
| `Wait-PveTask` | `/nodes/{node}/tasks/{upid}/status` (polling) | GET | Open |
### Missing API Coverage by Functional Area
| Area | Key Missing Endpoints | Priority | Prior Status |
|---|---|---|---|
| **Firewall** | `/cluster/firewall/*`, `/nodes/{node}/firewall/*`, `/nodes/{node}/qemu/{vmid}/firewall/*` | High | Open |
| **Backup / vzdump** | `/nodes/{node}/vzdump`, `/cluster/backup/*` | High | Open |
| **Pools** | `/pools`, `/pools/{poolid}` (CRUD) | Medium | Open |
| **HA** | `/cluster/ha/*` (groups, resources, status) | Medium | Open |
| **Ceph** | `/nodes/{node}/ceph/*` (OSD, pool, mon, fs) | Medium | Open |
| **Replication** | `/cluster/replication` (CRUD) | Low | Open |
| **Access Groups** | `/access/groups` (CRUD) | Low | Open |
| **Access Domains** | `/access/domains` (CRUD) | Low | Open |
| **PBS Integration** | Proxmox Backup Server operations | Low | Open |
| **Cluster Config** | `/cluster/config/*` (join, nodes, totem) | Low | Open |
| **ACME** | `/cluster/acme/*`, `/nodes/{node}/certificates/*` | Low | Open |
| **Node Management** | `/nodes/{node}/apt/*`, `/nodes/{node}/disks/*`, `/nodes/{node}/services/*` | Low | Open |
| **Metrics** | `/cluster/metrics/*` | Low | Open |
| **SDN IPAM/DNS/Controllers** | `/cluster/sdn/ipams/*`, `/cluster/sdn/dns/*`, `/cluster/sdn/controllers/*` | Low | New |
| **VM Agent (extended)** | Additional agent endpoints (file-read, file-write, os-info) | Low | Open |
### High-Value Gaps
1. **Firewall management** — Critical for security automation. No cmdlets for creating/managing firewall rules at cluster, node, or VM level.
2. **Backup/restore (vzdump)** — Essential for DR automation. No cmdlets for creating backups, managing schedules, or restoring.
3. **Pool management** — Required for multi-tenant environments. No cmdlets for creating/managing resource pools.
---
## Phase 3 — Code Quality & Best Practices
### 3a. PowerShell Module Design
#### Cmdlet Naming
- **All cmdlets use approved PowerShell verbs** via verb class constants (`VerbsCommon.Get`, `VerbsLifecycle.Start`, etc.).
- Noun prefix `Pve` is consistent across all 83 cmdlets.
- Previous finding about `Reset-PveVm` using a string literal has been fixed — now uses `VerbsCommon.Reset`.
#### Parameter Design
- `[Parameter(Mandatory = ...)]` used appropriately throughout.
- `ValueFromPipelineByPropertyName = true` used on `Node`, `VmId`, and similar parameters.
- `Position` attributes used on key parameters.
- `ValidateSet`, `ValidateRange(100, 999999999)`, `ValidateNotNullOrEmpty`, `ValidatePattern` used where appropriate.
- **HelpMessage** present on virtually all parameters (previously missing).
#### ShouldProcess / WhatIf / Confirm
- Every destructive/mutating cmdlet implements `SupportsShouldProcess = true`.
- `ConfirmImpact = ConfirmImpact.High` on all `Remove-*` cmdlets, `Stop-PveVm`, `Reset-PveVm`, `New-PveTemplate`, `Restore-PveSnapshot`, `Restore-PveContainerSnapshot`.
- **Gap**: `Remove-PveRole` does not set `ConfirmImpact.High` — deleting a role is a significant operation.
#### OutputType
- `[OutputType]` attributes present on all 83 cmdlets.
#### Pipeline Support
- All cmdlets use `ProcessRecord` for pipeline processing.
- `Get-PveVm`, `Get-PveContainer` output objects with `Node` and `VmId` enabling chaining.
#### Error Handling
- `ThrowTerminatingError` used appropriately for fatal conditions.
- `WriteWarning` used for non-fatal conditions.
- Custom exception hierarchy provides structured errors.
### 3b. C# Code Quality
#### Null-Reference Safety
- `<Nullable>enable</Nullable>` in both `.csproj` files.
- Consistent use of nullable annotations, null guards (`?? throw`), and null-coalescing patterns.
- No null-forgiving operators (`!`) used.
#### Async/Await Pattern
- All HTTP operations are async in `PveHttpClient` with `.ConfigureAwait(false)`.
- Sync wrappers use `.GetAwaiter().GetResult()` — standard for PowerShell binary modules.
- Low deadlock risk given the single pipeline thread model.
#### IDisposable Correctness
- `PveHttpClient` implements `IDisposable` correctly with `_disposed` flag.
- All service methods use `using var client = new PveHttpClient(session)`.
- File upload uses framework-conditional async disposal.
#### Exception Handling
- Well-defined exception hierarchy (7 custom types) with context properties.
- `PveHttpClient.SendAsync` wraps `HttpRequestException` in `PveApiException`.
- Silent catches are intentional and documented (node polling, status enrichment).
#### Magic Strings
- Auth header names inline (minor, not problematic).
- API resource paths constructed inline (pragmatic for codebase size).
- `RandomNumberGenerator` used for boundary generation (previously `new Random()`).
#### Logging / Verbose Output
- `WriteVerbose` used extensively throughout cmdlets (previously missing).
- `WriteProgress` used in `Send-PveFile`, `Import-PveOva`, and `Wait-PveTask`.
### 3c. General Hygiene
- No unused `using` directives detected.
- No commented-out blocks or dead code.
- Zero TODO/FIXME/HACK markers.
- Consistent code style matching `.editorconfig`.
- XML doc comments on all public types and cmdlet classes.
- `CS1591` no longer suppressed in Core project.
### Code Quality Findings
| ID | File | Line | Severity | Description | Prior Status |
|---|---|---|---|---|---|
| CQ1 | `RemovePveRoleCmdlet.cs` | 13 | Medium | Missing `ConfirmImpact.High` — deleting roles is significant | New |
| CQ2 | `PveHttpClient.cs` | 316-323 | Low | Auth header names (`PVEAPIToken=`, `CSRFPreventionToken`) could be constants | Open |
---
## Phase 4 — Testing Coverage Analysis
### Test Projects
| Project | Framework | Type | Runner |
|---|---|---|---|
| `PSProxmoxVE.Core.Tests` | xUnit 2.7 + Moq 4.20 | Unit tests (models, auth) | `dotnet test` |
| `PSProxmoxVE.Tests` | Pester 5 | Cmdlet + Integration tests | `Invoke-Pester` |
### xUnit Tests (Core Library)
| Test Area | Test File | ~Methods |
|---|---|---|
| Session lifecycle | `PveSessionTests.cs` | 9 |
| Authentication logic | `PveAuthenticatorTests.cs` | 7 |
| Version parsing | `PveVersionTests.cs` | 11 |
| Cluster models | `ClusterModelTests.cs` | 13 |
| Container models | `ContainerModelTests.cs` | 13 |
| Network models | `NetworkModelTests.cs` | 12 |
| Node models | `NodeModelTests.cs` | 13 |
| SDN models | `SdnModelTests.cs` | 17 |
| Snapshot models | `SnapshotModelTests.cs` | 9 |
| Storage models | `StorageModelTests.cs` | 19 |
| Task models | `TaskModelTests.cs` | 9 |
| User models | `UserModelTests.cs` | 24 |
| VM models | `VmModelTests.cs` | 18 |
**Total: ~174 xUnit tests** with PVE 8 and PVE 9 JSON fixtures.
### Pester Tests (30 files, ~500 tests)
All 83 cmdlets have Pester unit tests covering command metadata, parameter validation, ShouldProcess presence, and pipeline binding.
### Integration Tests
The integration test file covers 18+ major contexts with 90+ individual tests:
- Connection, Nodes, User/Role/Token CRUD, Permissions
- VM lifecycle (create, config, start/stop, reset, clone, resize, suspend/resume)
- Snapshot CRUD (VM and container)
- Storage (list, content, upload, create/remove, download)
- Network CRUD with apply/revert
- SDN zones/VNets/subnets CRUD
- Templates, Cloud-Init
- Container lifecycle (create, config, start/stop/restart, clone, snapshots)
- Linux VM provisioning (cloud image upload, disk import, cloud-init, guest agent)
**Setup/teardown**: AfterAll cleans up all created resources. Environment variables inject connection details. Tests skip gracefully when env vars are missing.
### Test Coverage Gap List
| Cmdlet | Unit Test | Integration Test | PVE 8 | PVE 9 | Prior Status |
|---|---|---|---|---|---|
| `Connect-PveServer` | ✓ | ✓ | ✓ | ✓ | Open |
| `Disconnect-PveServer` | ✓ | ✓ | ✓ | ✓ | Open |
| `Test-PveConnection` | ✓ | ✓ | ✓ | ✓ | Open |
| `Get-PveNode` | ✓ | ✓ | ✓ | ✓ | Open |
| `Get-PveNodeStatus` | ✓ | ✓ | ✓ | ✓ | Open |
| `Get-PveVm` | ✓ | ✓ | ✓ | ✓ | Open |
| `New-PveVm` | ✓ | ✓ | — | ✓ | Open |
| `Remove-PveVm` | ✓ | ✓ | — | ✓ | Open |
| `Start-PveVm` | ✓ | ✓ | ✓ | ✓ | Open |
| `Stop-PveVm` | ✓ | ✓ | ✓ | ✓ | Open |
| `Restart-PveVm` | ✓ | ✓ | ✓ | ✓ | Open |
| `Suspend-PveVm` | ✓ | ✓ | — | ✓ | Fixed |
| `Resume-PveVm` | ✓ | ✓ | — | ✓ | Fixed |
| `Reset-PveVm` | ✓ | ✓ | ✓ | ✓ | Open |
| `Copy-PveVm` | ✓ | ✓ | — | ✓ | Open |
| `Move-PveVm` | ✓ | ✗ | — | — | Open |
| `Get-PveVmConfig` | ✓ | ✓ | — | ✓ | Open |
| `Set-PveVmConfig` | ✓ | ✓ | — | ✓ | Open |
| `Resize-PveVmDisk` | ✓ | ✓ | — | ✓ | Fixed |
| `Import-PveVmDisk` | ✓ | ✓ | — | ✓ | New |
| `Import-PveOva` | ✓ | ✗ | — | — | New |
| `Test-PveVmGuestAgent` | ✓ | ✓ | — | ✓ | Open |
| `Get-PveVmGuestNetwork` | ✓ | ✓ | — | ✓ | Open |
| `Invoke-PveVmGuestExec` | ✓ | ✓ | — | ✓ | Open |
| `Get-PveContainer` | ✓ | ✓ | — | ✓ | Open |
| `New-PveContainer` | ✓ | ✓ | — | ✓ | Open |
| `Remove-PveContainer` | ✓ | ✓ | — | ✓ | Open |
| `Start-PveContainer` | ✓ | ✓ | — | ✓ | Open |
| `Stop-PveContainer` | ✓ | ✓ | — | ✓ | Open |
| `Restart-PveContainer` | ✓ | ✓ | — | ✓ | Open |
| `Copy-PveContainer` | ✓ | ✓ | — | ✓ | Fixed |
| `Move-PveContainer` | ✓ | ✗ | — | — | New |
| `Get-PveContainerConfig` | ✓ | ✓ | — | ✓ | Open |
| `Set-PveContainerConfig` | ✓ | ✓ | — | ✓ | Open |
| `Get-PveContainerSnapshot` | ✓ | ✓ | — | ✓ | New |
| `New-PveContainerSnapshot` | ✓ | ✓ | — | ✓ | New |
| `Remove-PveContainerSnapshot` | ✓ | ✓ | — | ✓ | New |
| `Restore-PveContainerSnapshot` | ✓ | ✓ | — | ✓ | New |
| `Get-PveStorage` | ✓ | ✓ | ✓ | ✓ | Open |
| `New-PveStorage` | ✓ | ✓ | — | ✓ | Fixed |
| `Remove-PveStorage` | ✓ | ✓ | — | ✓ | Fixed |
| `Get-PveStorageContent` | ✓ | ✓ | ✓ | ✓ | Open |
| `Send-PveFile` | ✓ | ✓ | — | ✓ | Open |
| `Invoke-PveStorageDownload` | ✓ | ✓ | — | ✓ | Fixed |
| `Get-PveSnapshot` | ✓ | ✓ | ✓ | ✓ | Open |
| `New-PveSnapshot` | ✓ | ✓ | ✓ | ✓ | Open |
| `Remove-PveSnapshot` | ✓ | ✓ | ✓ | ✓ | Open |
| `Restore-PveSnapshot` | ✓ | ✗ | — | — | Open |
| `Get-PveNetwork` | ✓ | ✓ | — | ✓ | Open |
| `New-PveNetwork` | ✓ | ✓ | — | ✓ | Fixed |
| `Set-PveNetwork` | ✓ | ✓ | — | ✓ | Fixed |
| `Remove-PveNetwork` | ✓ | ✓ | — | ✓ | Fixed |
| `Invoke-PveNetworkApply` | ✓ | ✓ | — | ✓ | Fixed |
| `Get-PveSdnZone` | ✓ | ✓ | — | ✓ | Open |
| `New-PveSdnZone` | ✓ | ✓ | — | ✓ | Fixed |
| `Remove-PveSdnZone` | ✓ | ✓ | — | ✓ | Fixed |
| `Get-PveSdnVnet` | ✓ | ✓ | — | ✓ | Open |
| `New-PveSdnVnet` | ✓ | ✓ | — | ✓ | Fixed |
| `Remove-PveSdnVnet` | ✓ | ✓ | — | ✓ | Fixed |
| `Get-PveSdnSubnet` | ✓ | ✓ | — | ✓ | New |
| `New-PveSdnSubnet` | ✓ | ✓ | — | ✓ | New |
| `Remove-PveSdnSubnet` | ✓ | ✓ | — | ✓ | New |
| `Get-PveUser` | ✓ | ✓ | ✓ | ✓ | Open |
| `New-PveUser` | ✓ | ✓ | ✓ | ✓ | Open |
| `Set-PveUser` | ✓ | ✓ | ✓ | ✓ | Open |
| `Remove-PveUser` | ✓ | ✓ | ✓ | ✓ | Open |
| `Get-PveRole` | ✓ | ✓ | ✓ | ✓ | Open |
| `New-PveRole` | ✓ | ✓ | ✓ | ✓ | Open |
| `Remove-PveRole` | ✓ | ✓ | ✓ | ✓ | Open |
| `Get-PveApiToken` | ✓ | ✓ | ✓ | ✓ | Open |
| `New-PveApiToken` | ✓ | ✓ | ✓ | ✓ | Open |
| `Remove-PveApiToken` | ✓ | ✓ | ✓ | ✓ | Open |
| `Get-PvePermission` | ✓ | ✓ | — | ✓ | Open |
| `Set-PvePermission` | ✓ | ✓ | — | ✓ | Open |
| `Get-PveTemplate` | ✓ | ✓ | — | ✓ | Open |
| `New-PveTemplate` | ✓ | ✗ | — | — | Open |
| `Remove-PveTemplate` | ✓ | ✗ | — | — | Open |
| `New-PveVmFromTemplate` | ✓ | ✗ | — | — | Open |
| `Get-PveCloudInitConfig` | ✓ | ✓ | — | ✓ | Open |
| `Set-PveCloudInitConfig` | ✓ | ✓ | — | ✓ | Fixed |
| `Invoke-PveCloudInitRegenerate` | ✓ | ✓ | — | ✓ | Fixed |
| `Get-PveTask` | ✓ | ✓ | ✓ | ✓ | Open |
| `Wait-PveTask` | ✓ | ✓ | — | ✓ | Open |
**Summary**: 83/83 unit tested (100%), 70/83 integration tested (84%). Remaining gaps are for operations requiring multi-node clusters (`Move-PveVm`, `Move-PveContainer`), optional test assets (`Import-PveOva`), or template operations that would require extra setup.
---
## Phase 5 — Security Review
### 5.1 Credential Handling
| Aspect | Status | Details | Prior Status |
|---|---|---|---|
| PSCredential usage | **Good** | `Connect-PveServer` accepts `PSCredential` for password auth | Open |
| Password extraction | **Acceptable** | `NetworkCredential.Password` used only at API call time | Open |
| API token in memory | **Acceptable** | Stored as plain `string` in `PveSession.ApiToken` | Open |
| Credential logging | **Good** | No `WriteVerbose`/`WriteDebug` calls include credentials | Open |
| Ticket storage | **Good** | Tickets stored in `PveSession` only, not written to disk | Open |
| Session expiry | **Good** | 2-hour expiry detected via `IsExpired` property | Open |
### 5.2 TLS/HTTPS
| Aspect | Status | Details | Prior Status |
|---|---|---|---|
| HTTPS enforced | **Good** | `BaseUrl` always constructs `https://` URLs | Open |
| SkipCertificateCheck | **Good** | Explicit opt-in parameter on `Connect-PveServer` | Open |
| Warning on skip | **Good** | `WriteWarning` emitted with man-in-the-middle advisory | Fixed |
| TLS version | **Good** | Left to OS/runtime negotiation (no pinning) | Open |
| Port validation | **Good** | `ValidateRange(1, 65535)` on port parameter | Open |
### 5.3 Input Validation
| Aspect | Status | Details | Prior Status |
|---|---|---|---|
| VMID validation | **Good** | `[ValidateRange(100, 999999999)]` on all VmId parameters | Fixed |
| URL construction | **Good** | String interpolation with validated parameters | Open |
| User ID encoding | **Good** | `Uri.EscapeDataString()` used for user/token IDs in URLs | Open |
| API token format | **Good** | Regex validation in `PveAuthenticator` | Open |
| Path parameters | **Low risk** | Node/snapshot names not URL-encoded but come from validated sources | Open |
### 5.4 Dependency Security
| Package | Version | Pinned | Notes | Prior Status |
|---|---|---|---|---|
| `Newtonsoft.Json` | 13.0.3 | Yes | Latest stable, no known CVEs | Open |
| `System.Text.Json` | 8.0.5 | Yes | Latest 8.x patch | Open |
| `SharpCompress` | 0.38.0 | Yes | OVA/tar extraction support | New |
| `PowerShellStandard.Library` | 5.1.1 | Yes | PrivateAssets=all | Open |
| `System.Management.Automation` | 7.4.0 | Yes | PrivateAssets=all | Open |
All dependencies are pinned to exact versions.
### 5.5 Secret Scanning
No hardcoded credentials, tokens, or sensitive IPs found in source code. CI workflow uses GitHub Actions secrets with `::add-mask::` for test passwords.
### Security Findings
| ID | Area | File | Severity | Description | Prior Status |
|---|---|---|---|---|---|
| S1 | Input validation | `SnapshotService.cs`, `ContainerService.cs` | Low | Snapshot names and node names in URL paths are not URL-encoded — low risk as values come from validated/system sources | New |
---
## Phase 6 — PSGallery Publication Readiness
### 6.1 Module Manifest Assessment
| Field | Status | Value | Prior Status |
|---|---|---|---|
| `RootModule` | Pass | `PSProxmoxVE.dll` | Open |
| `ModuleVersion` | Pass | `0.1.0` | Open |
| `GUID` | Pass | `a3f7c2d1-84e5-4b9f-a061-3e2d8c5f1a7b` | Open |
| `Author` | Pass | `goodolclint` | Open |
| `CompanyName` | Pass | `Worklab` | Open |
| `Copyright` | Pass | `(c) 2026 goodolclint. All rights reserved.` | Open |
| `Description` | Pass | Meaningful | Open |
| `PowerShellVersion` | Pass | `5.1` | Open |
| `CompatiblePSEditions` | Pass | `Desktop`, `Core` | Open |
| `DotNetFrameworkVersion` | Pass | `4.8` | Open |
| `RequiredAssemblies` | Pass | Lists Core DLL and Newtonsoft.Json | Open |
| `FormatsToProcess` | Pass | `PSProxmoxVE.format.ps1xml` | Open |
| `CmdletsToExport` | Pass | Explicit list of all cmdlets | Open |
| `FunctionsToExport` | Pass | Empty (binary module) | Open |
| `AliasesToExport` | Pass | 7 aliases defined | New |
| `Prerelease` | Pass | `preview` | Open |
| `Tags` | Pass | 8 relevant tags | Open |
| `LicenseUri` | Pass | GitHub LICENSE | Open |
| `ProjectUri` | Pass | GitHub repo | Open |
| `HelpInfoURI` | Pass | `docs/cmdlets` | Fixed |
| `ReleaseNotes` | Pass | Present in PSData | Fixed |
| `IconUri` | **Fail** | Not present | Open |
### 6.2 License
| Check | Pass/Fail | Notes | Prior Status |
|---|---|---|---|
| LICENSE file present | Pass | MIT License at repo root | Open |
| OSI-approved license | Pass | MIT | Open |
| LicenseUri in manifest | Pass | Points to GitHub LICENSE | Open |
### 6.3 README Quality
| Check | Pass/Fail | Notes | Prior Status |
|---|---|---|---|
| Installation instructions | Pass | `Install-Module PSProxmoxVE` | Fixed |
| Quick-start examples | Pass | Multiple scenarios | Open |
| Authentication guide | Pass | Ticket + API token documented | Open |
| Badges | Pass | Build, Unit Tests, License badges | Fixed |
| Cmdlet reference | Pass | All cmdlets listed | Open |
| Known limitations | Pass | Documented | Open |
| Contributing section | Pass | References CONTRIBUTING.md | Open |
### 6.4 Build & Publish Pipeline
| Check | Pass/Fail | Notes | Prior Status |
|---|---|---|---|
| Build workflow | Pass | net48 + net9.0, Windows + Ubuntu | Open |
| Unit test workflow | Pass | PS 5.1/7.5, Windows/Ubuntu/macOS | Open |
| Integration test workflow | Pass | PVE 8 + PVE 9 | Open |
| Publish workflow | Pass | Tag-triggered, PSGallery + GitHub Release | Fixed |
| API key handling | Pass | Via `PSGALLERY_API_KEY` secret | Fixed |
### 6.5 Versioning
| Check | Pass/Fail | Notes | Prior Status |
|---|---|---|---|
| CHANGELOG format | Pass | Keep a Changelog with versioned entry | Open |
| Versioned release | Pass | `0.1.0-preview` entry present | Fixed |
| Commit conventions | Pass | Conventional Commits used consistently | Open |
### PSGallery Readiness Checklist
| Check | Pass/Fail | Notes | Prior Status |
|---|---|---|---|
| Module manifest complete | Pass | All required fields populated | Open |
| License present and OSI-approved | Pass | MIT | Open |
| README with install instructions | Pass | PSGallery install documented | Fixed |
| Publish pipeline configured | Pass | `publish.yml` on tag push | Fixed |
| ReleaseNotes present | Pass | In PSData section | Fixed |
| IconUri | **Fail** | Missing — cosmetic only | Open |
| MAML help file | Pass | `PSProxmoxVE.dll-Help.xml` present | New |
---
## Phase 7 — Community & Repo Maintenance Standards
| Check | Pass/Fail | Notes | Prior Status |
|---|---|---|---|
| `README.md` | Pass | Comprehensive with badges | Open |
| `LICENSE` | Pass | MIT | Open |
| `CHANGELOG.md` | Pass | Keep a Changelog format with versioned entry | Open |
| `CONTRIBUTING.md` | Pass | Dev setup, coding standards, test instructions, PR process | Fixed |
| `CODE_OF_CONDUCT.md` | Pass | Contributor Covenant v2.1 | Fixed |
| `SECURITY.md` | Pass | Vulnerability disclosure policy, 48h response SLA | Fixed |
| `.editorconfig` | Pass | Comprehensive rules | Open |
| `.gitignore` | Pass | Build outputs, IDE files, test results | Open |
| `.gitattributes` | Pass | Line ending normalization, binary detection | Fixed |
| Issue templates | Pass | Bug report + feature request (YAML format) | Fixed |
| PR template | Pass | Summary, type checkboxes, checklist | Fixed |
| Conventional commits | Pass | Consistent in recent history | Open |
| Default branch `main` | Pass | Confirmed | Open |
| API coverage documentation | Pass | `docs/PVE_API_COVERAGE.md` | New |
| Cmdlet documentation | Pass | 75 files in `docs/cmdlets/` | New |
### Recommended Branch Protection (cannot verify via API)
- Require PR reviews before merge
- Require status checks (build + unit tests + integration tests)
- Require linear history or squash merging
---
## Phase 8 — Prioritized Recommendations
### 🔴 Critical (blocks PSGallery publication or is a security risk)
**None** — all prior Critical items have been resolved.
### 🟠 High (significantly impacts quality or community adoption)
| # | What | Where | Why | Fix | Prior Status |
|---|---|---|---|---|---|
| H1 | **No firewall cmdlets** | Module | High-value gap for security automation — most requested feature area | Implement firewall rule CRUD for cluster/node/VM | Open |
| H2 | **No backup/vzdump cmdlets** | Module | Essential for DR automation scenarios | Implement vzdump and backup schedule management | Open |
### 🟡 Medium (best practice gaps, test coverage holes)
| # | What | Where | Why | Fix | Prior Status |
|---|---|---|---|---|---|
| M1 | `Remove-PveRole` missing `ConfirmImpact.High` | `RemovePveRoleCmdlet.cs:13` | Deleting roles is a significant, potentially disruptive operation | Add `ConfirmImpact = ConfirmImpact.High` | New |
| M2 | No pool management | Module | Useful for multi-tenant environments | Implement pool CRUD | Open |
| M3 | `Move-PveVm` / `Move-PveContainer` lack integration tests | Integration tests | Migration is untested end-to-end (requires multi-node) | Add integration tests when multi-node CI is available | Open |
| M4 | `Import-PveOva` lacks integration test | Integration tests | OVA import only tested at parameter level | Add to integration tests (requires OVA test asset) | New |
| M5 | URL-encode snapshot/node names in API paths | `SnapshotService.cs`, `ContainerService.cs` | Defense-in-depth against path injection (low risk) | Apply `Uri.EscapeDataString()` to path parameters | New |
### 🟢 Low / Nice-to-have (polish, discoverability, stretch goals)
| # | What | Where | Why | Fix | Prior Status |
|---|---|---|---|---|---|
| L1 | No `IconUri` in manifest | `PSProxmoxVE.psd1` | Improves PSGallery listing appearance | Create module icon, host it, add URI | Open |
| L2 | Auth header magic strings | `PveHttpClient.cs:316-323` | Minor code quality — could be named constants | Extract to `const string` fields | Open |
| L3 | HA/Ceph/Replication cmdlets | Module | Lower-priority API coverage gaps | Implement as demand grows | Open |
| L4 | Access groups/domains cmdlets | Module | Useful for LDAP/AD integration | Implement `/access/groups`, `/access/domains` CRUD | Open |
| L5 | PSGallery version badge | `README.md` | Shows published version to visitors | Add PSGallery badge after first publish | New |
| L6 | SDN IPAM/DNS/Controller cmdlets | Module | Complete SDN management surface | Implement as demand grows | New |
---
## Summary Statistics
| Metric | This Scan | Prior Scan | Delta |
|---|---|---|---|
| Total cmdlets | 83 | 66 | +17 |
| Cmdlets with ShouldProcess | All mutating | All mutating | — |
| Cmdlets with OutputType | 83 (100%) | 66 (100%) | — |
| Cmdlets with HelpMessage | 83 (100%) | 0 (0%) | +83 |
| xUnit test files | 13 | 13 | — |
| Pester test files | 30 | 26 | +4 |
| Integration test coverage | 84% | ~70% | +14% |
| PVE API areas covered | 12 of ~20 | 10 of ~20 | +2 |
| Critical issues | 0 | 3 | -3 |
| High issues | 2 | 8 | -6 |
| Medium issues | 5 | 10 | -5 |
| Low issues | 6 | 10 | -4 |
| **Total issues** | **13** | **31** | **-18** |
| NuGet dependencies (runtime) | 3 | 2 | +1 (SharpCompress) |
| Security vulnerabilities | 0 | 0 | — |
| Community files present | 9/9 | 3/9 | +6 |
| Documentation files | 78 | 1 | +77 |