# 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 - `enable` 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 |