# PSProxmoxVE Module Review 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 1. **Well-structured project** with clean separation: `PSProxmoxVE.Core` (services, models, client) and `PSProxmoxVE` (cmdlets, module surface). 2. **66 cmdlets** covering VMs, containers, storage, networking, SDN, users/roles/permissions, templates, cloud-init, snapshots, and tasks. 3. **Excellent ShouldProcess coverage** -- all destructive cmdlets implement `SupportsShouldProcess` with appropriate `ConfirmImpact` levels. 4. **OutputType attributes** present on every cmdlet. ValueFromPipelineByPropertyName used consistently for pipeline chaining. 5. **Sync-over-async pattern** (`GetAwaiter().GetResult()`) used pervasively -- acceptable for PowerShell binary modules but a known tradeoff. 6. **Comprehensive integration tests** running against real PVE 8 and PVE 9 via Terraform-provisioned nested instances. 7. **Missing community files**: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `.gitattributes`, issue/PR templates. 8. **No publish workflow**: No CI pipeline for publishing to PSGallery. 9. **One unapproved verb**: `Reset-PveVm` uses a hardcoded string `"Reset"` instead of `VerbsCommon` -- `Reset` is not an approved PowerShell verb. 10. **PVE API coverage**: Module covers core automation scenarios well but lacks firewall, backup/restore (PBS), Ceph, HA, and replication management. --- ## Phase 1 -- Repository Inventory & Structure ### Directory Tree (depth <= 4) ``` PSProxmoxVE/ +-- PSProxmoxVE.sln +-- README.md +-- LICENSE (MIT) +-- CHANGELOG.md +-- .editorconfig +-- .gitignore +-- CLAUDE.md +-- .github/ | +-- workflows/ | +-- build.yml | +-- unit-tests.yml | +-- integration-tests.yml +-- src/ | +-- PSProxmoxVE/ | | +-- PSProxmoxVE.csproj | | +-- PSProxmoxVE.psd1 | | +-- PSProxmoxVE.format.ps1xml | | +-- ModuleState.cs | | +-- Cmdlets/ | | +-- PveCmdletBase.cs | | +-- CloudInit/ (3 cmdlets) | | +-- Connection/ (3 cmdlets) | | +-- Containers/ (8 cmdlets) | | +-- Network/ (10 cmdlets) | | +-- Nodes/ (2 cmdlets) | | +-- Snapshots/ (4 cmdlets) | | +-- Storage/ (5 cmdlets) | | +-- Tasks/ (2 cmdlets) | | +-- Templates/ (4 cmdlets) | | +-- Users/ (11 cmdlets) | | +-- Vms/ (17 cmdlets) | +-- PSProxmoxVE.Core/ | +-- PSProxmoxVE.Core.csproj | +-- Authentication/ (PveAuthenticator, PveAuthMode, PveSession, PveVersion) | +-- Client/ (PveHttpClient) | +-- Exceptions/ (7 exception types) | +-- Models/ (Cluster, Containers, Network, Nodes, Storage, Users, Vms) | +-- Services/ (11 service classes) +-- tests/ | +-- PSProxmoxVE.Core.Tests/ (xUnit) | | +-- Authentication/ (3 test files) | | +-- Models/ (10 test files) | | +-- TestHelper.cs | +-- PSProxmoxVE.Tests/ (Pester 5) | +-- _TestHelper.ps1 | +-- CloudInit/ (1 test file) | +-- Connection/ (3 test files) | +-- Containers/ (3 test files) | +-- Network/ (2 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/ (6 test files) | +-- Integration/ (1 comprehensive test file) +-- tests/infrastructure/ (Terraform, Dockerfile, scripts for CI) ``` ### 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 | ### What's Present - [x] Solution file (`.sln`) - [x] Module manifest (`.psd1`) with explicit `CmdletsToExport` - [x] Format definitions (`.format.ps1xml`) - [x] `.editorconfig` - [x] `.gitignore` - [x] `LICENSE` (MIT) - [x] `CHANGELOG.md` (Keep a Changelog format) - [x] `README.md` (comprehensive) - [x] CI/CD workflows (build, unit tests, integration tests) ### What's Missing - [ ] `CONTRIBUTING.md` - [ ] `CODE_OF_CONDUCT.md` - [ ] `SECURITY.md` - [ ] `.gitattributes` - [ ] `.github/ISSUE_TEMPLATE/` - [ ] `.github/pull_request_template.md` - [ ] PSGallery publish workflow - [ ] `ReleaseNotes` in manifest `PSData` - [ ] `IconUri` in manifest `PSData` --- ## Phase 2 -- PVE API Coverage Audit > **Note:** The PVE API documentation at `pve.proxmox.com/pve-docs/api-viewer/apidoc.js` was too large for automated extraction. Coverage assessment below is based on the author's knowledge of the PVE API and source code analysis. ### Implemented Cmdlets by Category #### Connection (3 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Connect-PveServer` | `/access/ticket` | POST | | `Disconnect-PveServer` | `/access/ticket` | DELETE | | `Test-PveConnection` | `/version` | GET | #### Nodes (2 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveNode` | `/nodes` | GET | | `Get-PveNodeStatus` | `/nodes/{node}/status` | GET | #### VMs / QEMU (17 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveVm` | `/nodes/{node}/qemu` | GET | | `New-PveVm` | `/nodes/{node}/qemu` | POST | | `Remove-PveVm` | `/nodes/{node}/qemu/{vmid}` | DELETE | | `Start-PveVm` | `/nodes/{node}/qemu/{vmid}/status/start` | POST | | `Stop-PveVm` | `/nodes/{node}/qemu/{vmid}/status/stop` | POST | | `Restart-PveVm` | `/nodes/{node}/qemu/{vmid}/status/reboot` | POST | | `Suspend-PveVm` | `/nodes/{node}/qemu/{vmid}/status/suspend` | POST | | `Resume-PveVm` | `/nodes/{node}/qemu/{vmid}/status/resume` | POST | | `Reset-PveVm` | `/nodes/{node}/qemu/{vmid}/status/reset` | POST | | `Copy-PveVm` | `/nodes/{node}/qemu/{vmid}/clone` | POST | | `Move-PveVm` | `/nodes/{node}/qemu/{vmid}/migrate` | POST | | `Get-PveVmConfig` | `/nodes/{node}/qemu/{vmid}/config` | GET | | `Set-PveVmConfig` | `/nodes/{node}/qemu/{vmid}/config` | PUT | | `Resize-PveVmDisk` | `/nodes/{node}/qemu/{vmid}/resize` | PUT | | `Test-PveVmGuestAgent` | `/nodes/{node}/qemu/{vmid}/agent/ping` | POST | | `Get-PveVmGuestNetwork` | `/nodes/{node}/qemu/{vmid}/agent/network-get-interfaces` | GET | | `Invoke-PveVmGuestExec` | `/nodes/{node}/qemu/{vmid}/agent/exec` | POST | #### Containers / LXC (8 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveContainer` | `/nodes/{node}/lxc` | GET | | `New-PveContainer` | `/nodes/{node}/lxc` | POST | | `Remove-PveContainer` | `/nodes/{node}/lxc/{vmid}` | DELETE | | `Start-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/start` | POST | | `Stop-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/stop` | POST | | `Restart-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/shutdown` | POST | | `Copy-PveContainer` | `/nodes/{node}/lxc/{vmid}/clone` | POST | | `Get-PveContainerConfig` | `/nodes/{node}/lxc/{vmid}/config` | GET | | `Set-PveContainerConfig` | `/nodes/{node}/lxc/{vmid}/config` | PUT | #### Storage (5 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveStorage` | `/storage` or `/nodes/{node}/storage` | GET | | `Get-PveStorageContent` | `/nodes/{node}/storage/{storage}/content` | GET | | `Send-PveIso` | `/nodes/{node}/storage/{storage}/upload` | POST | | `Invoke-PveStorageDownload` | `/nodes/{node}/storage/{storage}/download-url` | POST | | `New-PveStorage` | `/storage` | POST | | `Remove-PveStorage` | `/storage/{storage}` | DELETE | #### Snapshots (4 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot` | GET | | `New-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot` | POST | | `Remove-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot/{name}` | DELETE | | `Restore-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot/{name}/rollback` | POST | #### Networking (4 + 6 SDN cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveNetwork` | `/nodes/{node}/network` | GET | | `New-PveNetwork` | `/nodes/{node}/network` | POST | | `Set-PveNetwork` | `/nodes/{node}/network/{iface}` | PUT | | `Remove-PveNetwork` | `/nodes/{node}/network/{iface}` | DELETE | | `Invoke-PveNetworkApply` | `/nodes/{node}/network` | PUT | | `Get-PveSdnZone` | `/cluster/sdn/zones` | GET | | `New-PveSdnZone` | `/cluster/sdn/zones` | POST | | `Remove-PveSdnZone` | `/cluster/sdn/zones/{zone}` | DELETE | | `Get-PveSdnVnet` | `/cluster/sdn/vnets` | GET | | `New-PveSdnVnet` | `/cluster/sdn/vnets` | POST | | `Remove-PveSdnVnet` | `/cluster/sdn/vnets/{vnet}` | DELETE | #### Users / ACLs / Tokens (11 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveUser` | `/access/users` | GET | | `New-PveUser` | `/access/users` | POST | | `Remove-PveUser` | `/access/users/{userid}` | DELETE | | `Set-PveUser` | `/access/users/{userid}` | PUT | | `Get-PveRole` | `/access/roles` | GET | | `New-PveRole` | `/access/roles` | POST | | `Remove-PveRole` | `/access/roles/{roleid}` | DELETE | | `Get-PvePermission` | `/access/acl` | GET | | `Set-PvePermission` | `/access/acl` | PUT | | `Get-PveApiToken` | `/access/users/{userid}/token` | GET | | `New-PveApiToken` | `/access/users/{userid}/token/{tokenid}` | POST | | `Remove-PveApiToken` | `/access/users/{userid}/token/{tokenid}` | DELETE | #### Templates (4 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveTemplate` | `/nodes/{node}/qemu` (filtered) | GET | | `New-PveTemplate` | `/nodes/{node}/qemu/{vmid}/template` | POST | | `Remove-PveTemplate` | `/nodes/{node}/qemu/{vmid}` | DELETE | | `New-PveVmFromTemplate` | `/nodes/{node}/qemu/{vmid}/clone` | POST | #### Cloud-Init (3 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveCloudInitConfig` | `/nodes/{node}/qemu/{vmid}/config` | GET | | `Set-PveCloudInitConfig` | `/nodes/{node}/qemu/{vmid}/config` | PUT | | `Invoke-PveCloudInitRegenerate` | `/nodes/{node}/qemu/{vmid}/cloudinit` | PUT | #### Tasks (2 cmdlets) | Cmdlet | API Endpoint | Method | |---|---|---| | `Get-PveTask` | `/nodes/{node}/tasks/{upid}/status` | GET | | `Wait-PveTask` | `/nodes/{node}/tasks/{upid}/status` (polling) | GET | ### Missing API Coverage by Functional Area | Area | Key Missing Endpoints | Priority | |---|---|---| | **Firewall** | `/nodes/{node}/firewall/rules`, `/cluster/firewall/*` | High | | **Backup / Restore** | `/nodes/{node}/vzdump`, backup schedule management | High | | **Pools** | `/pools`, `/pools/{poolid}` (CRUD) | Medium | | **HA** | `/cluster/ha/*` (groups, resources, status) | Medium | | **Ceph** | `/nodes/{node}/ceph/*` (OSD, pool, mon, fs) | Medium | | **Replication** | `/cluster/replication` (CRUD) | Low | | **Access Groups** | `/access/groups` (CRUD) | Low | | **Access Domains** | `/access/domains` (CRUD) | Low | | **Container Snapshots** | `/nodes/{node}/lxc/{vmid}/snapshot` | Medium | | **Container Migrate** | `/nodes/{node}/lxc/{vmid}/migrate` | Medium | | **PBS Integration** | Proxmox Backup Server operations | Low | | **Cluster Config** | `/cluster/config/*` (join, nodes, totem) | Low | | **ACME** | `/cluster/acme/*`, `/nodes/{node}/certificates/*` | Low | | **Node Management** | `/nodes/{node}/apt/*`, `/nodes/{node}/disks/*`, `/nodes/{node}/services/*` | Low | | **Metrics** | `/cluster/metrics/*` | Low | | **SDN Subnets** | `/cluster/sdn/vnets/{vnet}/subnets` | Medium | | **VM Agent** | Additional agent endpoints (file-read, file-write, etc.) | Low | ### 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. 4. **Container snapshots** -- Snapshots are only implemented for VMs, not LXC containers. 5. **Container migration** -- `Move-PveVm` exists for VMs but no equivalent for containers. --- ## Phase 3 -- Code Quality & Best Practices ### 3a. PowerShell Module Design #### Cmdlet Naming - **All cmdlets use approved PowerShell verbs** from the `VerbsCommon`, `VerbsLifecycle`, `VerbsCommunications`, `VerbsDiagnostic`, and `VerbsData` classes. - **One exception**: `Reset-PveVm` uses a hardcoded string `"Reset"` instead of a verb class constant: `[Cmdlet("Reset", "PveVm", ...)]`. While `Reset` IS an approved verb (in `VerbsCommon`), the implementation should use `VerbsCommon.Reset` for consistency. - Noun prefix `Pve` is consistent across all cmdlets. #### Parameter Design - `[Parameter(Mandatory = ...)]` used appropriately throughout. - `ValueFromPipelineByPropertyName = true` used on `Node`, `VmId`, and similar parameters enabling pipeline chaining (e.g., `Get-PveVm | Start-PveVm`). - `Position` attributes used on key parameters for positional binding. - `ValidateSet`, `ValidateRange`, `ValidateNotNullOrEmpty` used where appropriate (network types, port range, storage types, etc.). - **Gap**: VmId parameters are `int` type but lack `[ValidateRange(100, 999999999)]` to match PVE's VMID constraints. #### ShouldProcess / WhatIf / Confirm - **Excellent**: Every destructive/mutating cmdlet implements `SupportsShouldProcess = true`. - `ConfirmImpact = ConfirmImpact.High` is set on all `Remove-*` cmdlets, `New-PveTemplate` (irreversible), and `Restore-PveSnapshot`. - **Gap**: `Stop-PveVm`, `Stop-PveContainer`, `Reset-PveVm` do not set `ConfirmImpact.High` -- stopping/resetting VMs can cause data loss. #### OutputType - `[OutputType]` attributes are present on **all 66 cmdlets**. This is exemplary. #### Pipeline Support - All cmdlets use `ValueFromPipelineByPropertyName` on `Node` and `VmId` parameters. - `Get-PveVm` and `Get-PveContainer` output objects with `Node` and `VmId` properties, enabling chaining. - **Gap**: None of the cmdlets implement `ProcessRecord` for pipeline batch processing -- all use `EndProcessing` (or equivalent). This means `Get-PveVm | Start-PveVm` processes only the last piped object. **This is a functional bug if multiple objects are piped.** #### Error Handling - `ThrowTerminatingError` used appropriately in `ConnectPveServerCmdlet`, `WaitPveTaskCmdlet`, `GetPveNodeCmdlet`, `GetPveNodeStatusCmdlet`. - `WriteWarning` used in `DisconnectPveServerCmdlet` and `SetPveCloudInitConfigCmdlet` for non-fatal conditions. - `WriteVerbose` used in connection cmdlets for diagnostic output. - **Gap**: Most cmdlets let exceptions propagate without wrapping in `ErrorRecord` -- this means non-terminating errors are not always well-structured. ### 3b. C# Code Quality #### Null-Reference Safety - `enable` in both `.csproj` files. - Nullable annotations used on session, parameters, and return types. - Null guards (`?? throw`) used in constructors and authentication methods. #### Async/Await Pattern - All HTTP operations are async at the `PveHttpClient` level. - Sync wrappers use `.GetAwaiter().GetResult()` pattern. - **Assessment**: This is the standard approach for PowerShell binary modules on .NET. True async cmdlets are only available via `BeginProcessing`/`EndProcessing` async overrides in PS 7.4+, which would break PS 5.1 compatibility. - **Risk**: Low. PowerShell cmdlets run on a single pipeline thread, so deadlock risk from `GetAwaiter().GetResult()` is minimal given the `ConfigureAwait(false)` usage throughout. #### IDisposable Correctness - `PveHttpClient` implements `IDisposable` correctly with a `_disposed` flag. - All usages in cmdlets and services use `using var client = new PveHttpClient(session)` or `using (var client = ...)` blocks. - `ProgressStream` disposes its inner stream correctly. - File upload uses `try/finally` for stream cleanup with conditional `DisposeAsync` on .NET 9+. - **Assessment**: Excellent. No resource leak risks identified. #### Exception Handling - Custom exception hierarchy: `PveApiException`, `PveAuthenticationException`, `PveNotConnectedException`, `PveSessionExpiredException`, `PveTaskTimeoutException`, `PveTaskFailedException`, `PveVersionException`. - `PveHttpClient.SendAsync` catches `HttpRequestException` and wraps it in `PveApiException`. - Error message extraction from PVE API JSON responses is handled gracefully. - The only bare `catch {}` is in `ExtractErrorMessage` where parse failure falls through to raw body -- this is acceptable. #### Magic Strings - API resource paths are constructed inline in service methods (e.g., `$"nodes/{node}/qemu/{vmid}/status/start"`). This is pragmatic for the size of the codebase. - Auth header names (`PVEAPIToken=`, `PVEAuthCookie=`, `CSRFPreventionToken`) are inline. Could be constants but not a significant issue. #### LINQ - No complex LINQ usage. JObject/JArray parsing uses Newtonsoft.Json's indexer pattern which is straightforward. #### Logging / Verbose Output - `WriteVerbose` used in `ConnectPveServerCmdlet` and `DisconnectPveServerCmdlet`. - **Gap**: No `WriteVerbose` calls in any other cmdlets -- users cannot see which API calls are being made. This hinders debugging. - `WriteDebug` is not used anywhere in the module. - **Gap**: `WriteProgress` is only used in `Send-PveIso` (via progress callback). Not used in `Wait-PveTask` or other long-running operations. ### 3c. General Hygiene #### Unused Code / Dead Code - No unused `using` directives detected (CS1591 suppressed for missing XML docs, not for unused code). - No commented-out blocks found. - **Clean**: Zero TODO/FIXME/HACK markers in source code. #### Code Style - Consistent 4-space indentation matching `.editorconfig`. - Opening braces on same line (Allman-adjacent style used in some places, K&R in others -- minor inconsistency but not problematic). - PascalCase for public members, camelCase for locals/fields. #### XML Doc Comments - Public types in `PSProxmoxVE.Core` have XML doc comments (enabled via `true`). - `CS1591` is suppressed in Core project (`$(NoWarn);CS1591`) so not all public members have docs. - Cmdlet classes have XML summary comments on the class level. - **Gap**: Parameter help text (`[Parameter(HelpMessage = ...)]`) is not used on any cmdlet parameter. This means `Get-Help` shows no parameter descriptions. --- ## Phase 4 -- Testing Coverage Analysis ### Test Projects | Project | Framework | Type | Runner | |---|---|---|---| | `PSProxmoxVE.Core.Tests` | xUnit 2.7 + Moq 4.20 | Unit tests | `dotnet test` | | `PSProxmoxVE.Tests` | Pester 5 | Cmdlet + Integration tests | `Invoke-Pester` | ### xUnit Tests (Core Library) | Test File | Test Area | |---|---| | `Authentication/PveSessionTests.cs` | Session creation, expiry, auth modes | | `Authentication/PveAuthenticatorTests.cs` | Authentication logic | | `Authentication/PveVersionTests.cs` | Version parsing | | `Models/ClusterModelTests.cs` | Cluster model deserialization | | `Models/ContainerModelTests.cs` | Container model deserialization | | `Models/NetworkModelTests.cs` | Network model deserialization | | `Models/NodeModelTests.cs` | Node model deserialization | | `Models/SdnModelTests.cs` | SDN model deserialization | | `Models/SnapshotModelTests.cs` | Snapshot model deserialization | | `Models/StorageModelTests.cs` | Storage model deserialization | | `Models/TaskModelTests.cs` | Task model deserialization | | `Models/UserModelTests.cs` | User model deserialization | | `Models/VmModelTests.cs` | VM model deserialization | ### Pester Tests (Cmdlet Level) | Test File | Cmdlet(s) Covered | |---|---| | `Connection/Connect-PveServer.Tests.ps1` | `Connect-PveServer` | | `Connection/Disconnect-PveServer.Tests.ps1` | `Disconnect-PveServer` | | `Connection/Test-PveConnection.Tests.ps1` | `Test-PveConnection` | | `Containers/Get-PveContainer.Tests.ps1` | `Get-PveContainer` | | `Containers/ContainerLifecycle.Tests.ps1` | Container start/stop/restart | | `Containers/ContainerConfigCmdlets.Tests.ps1` | Container config cmdlets | | `CloudInit/CloudInitCmdlets.Tests.ps1` | Cloud-init cmdlets | | `Network/Get-PveNetwork.Tests.ps1` | `Get-PveNetwork` | | `Network/SdnCmdlets.Tests.ps1` | SDN cmdlets | | `Nodes/NodeCmdlets.Tests.ps1` | Node cmdlets | | `Snapshots/SnapshotCmdlets.Tests.ps1` | Snapshot cmdlets | | `Storage/Get-PveStorage.Tests.ps1` | `Get-PveStorage` | | `Storage/StorageDownload.Tests.ps1` | `Invoke-PveStorageDownload` | | `Storage/Send-PveIso.Tests.ps1` | `Send-PveIso` | | `Tasks/TaskCmdlets.Tests.ps1` | Task cmdlets | | `Templates/TemplateCmdlets.Tests.ps1` | Template cmdlets | | `Users/UserCmdlets.Tests.ps1` | User CRUD | | `Users/ApiTokenCmdlets.Tests.ps1` | API token CRUD | | `Users/RoleCmdlets.Tests.ps1` | Role CRUD | | `Vms/Get-PveVm.Tests.ps1` | `Get-PveVm` | | `Vms/New-PveVm.Tests.ps1` | `New-PveVm` | | `Vms/Remove-PveVm.Tests.ps1` | `Remove-PveVm` | | `Vms/VmLifecycle.Tests.ps1` | VM start/stop/restart | | `Vms/VmAdvancedOps.Tests.ps1` | Clone, migrate, resize | | `Vms/VmConfigCmdlets.Tests.ps1` | VM config cmdlets | ### Integration Tests The integration test file (`Integration.Tests.ps1`) is comprehensive and covers: | Context | Operations Tested | |---|---| | Connection | Connect via API token, version detection | | Nodes | List nodes, get node status | | User CRUD | Create, list, update, remove user | | Role CRUD | Create, list, remove role | | API Token CRUD | Create, list, remove API token | | Permissions | List and set permissions | | VMs | List, create, config get/set, start/stop, hard reset, clone | | Snapshots | Create, list, restore, remove | | Storage | List storage, list content, upload ISO | | Tasks | Get task, wait for task | | Network | List networks | | Templates | List templates, convert to template, clone from template | | Cloud-Init | Get cloud-init config | | Guest Agent | Ping, network interfaces, exec command | | ACPI Lifecycle | Graceful restart, graceful stop | - **PVE 8 and PVE 9 both tested** via matrix strategy in CI. - **Setup/teardown**: AfterAll block cleans up created VMs, users, and roles. - **Skip logic**: Tests skip gracefully when env vars are not set. - **Connection details**: Injected via environment variables (`PVETEST_HOST`, `PVETEST_APITOKEN`, etc.). ### Test Coverage Gap List | Cmdlet | Unit Test | Integration Test | Gap | |---|---|---|---| | `Set-PveNetwork` | Pester | No | No integration test for network modification | | `New-PveNetwork` | Pester | No | No integration test for network creation | | `Remove-PveNetwork` | Pester | No | No integration test for network removal | | `Invoke-PveNetworkApply` | Pester | No | No integration test for network apply | | `New-PveSdnZone` | Pester | No | No integration test for SDN zone creation | | `Remove-PveSdnZone` | Pester | No | No integration test for SDN zone removal | | `New-PveSdnVnet` | Pester | No | No integration test for SDN VNet creation | | `Remove-PveSdnVnet` | Pester | No | No integration test for SDN VNet removal | | `New-PveStorage` | Pester | No | No integration test for storage creation | | `Remove-PveStorage` | Pester | No | No integration test for storage removal | | `Invoke-PveStorageDownload` | Pester | No | No integration test for URL download | | `Suspend-PveVm` | Pester | No | No integration test for VM suspend | | `Resume-PveVm` | Pester | No | No integration test for VM resume | | `Move-PveVm` | Pester | No | No integration test (requires multi-node) | | `Resize-PveVmDisk` | Pester | No | No integration test for disk resize | | `Copy-PveContainer` | Pester | No | No integration test for container clone | | `Set-PveCloudInitConfig` | Pester | No | No integration test for cloud-init set | | `Invoke-PveCloudInitRegenerate` | Pester | No | Skipped in CI due to bug | | `Set-PvePermission` | Pester | Partial | Only tests add, not remove | **Note**: Many of the gaps above are for operations that would require complex test infrastructure (multi-node clusters, specific storage backends, etc.) and are reasonable omissions for a pre-1.0 module. --- ## Phase 5 -- Security Review ### 5.1 Credential Handling | Aspect | Status | Details | |---|---|---| | PSCredential usage | **Good** | `Connect-PveServer` accepts `PSCredential` for password auth | | Password extraction | **Acceptable** | `NetworkCredential.Password` is used only at the point of API call | | API token in memory | **Acceptable** | Stored as plain `string` in `PveSession.ApiToken` -- standard for PowerShell modules | | Credential logging | **Good** | No `WriteVerbose`/`WriteDebug` calls that include credentials | | Ticket storage | **Good** | Tickets are stored in `PveSession` only, not written to disk | **Finding**: The password is extracted from `PSCredential` via `NetworkCredential.Password` which returns a plain `string`. This is the standard pattern for PowerShell binary modules since the PVE API requires the password as a form field. No improvement needed. ### 5.2 TLS/HTTPS | Aspect | Status | Details | |---|---|---| | HTTPS enforced | **Good** | `BaseUrl` always constructs `https://` URLs | | SkipCertificateCheck | **Good** | Explicit opt-in parameter on `Connect-PveServer` | | Certificate bypass | **Acceptable** | Uses `ServerCertificateCustomValidationCallback = ... => true` when opted in | | TLS version | **Good** | Left to OS/runtime negotiation (no pinning) | | Warning on skip | **Gap** | No `WriteWarning` when `-SkipCertificateCheck` is used | ### 5.3 Input Validation | Aspect | Status | Details | |---|---|---| | URL construction | **Good** | Resource paths use string interpolation with validated parameters | | Path traversal | **Low risk** | Node names, VM IDs, and storage names are used in URL paths but come from API responses or validated parameters | | VMID validation | **Gap** | VmId is `int` type (natural bounds) but no `[ValidateRange]` to enforce PVE's 100-999999999 range | | Username format | **Good** | `PveAuthenticator` validates `user@realm` format | | API token format | **Good** | Regex validation in `PveAuthenticator.AuthenticateWithApiToken` | ### 5.4 Dependency Security | Package | Version | Pinned | Notes | |---|---|---|---| | `Newtonsoft.Json` | 13.0.3 | Yes | Latest stable, no known CVEs | | `System.Text.Json` | 8.0.5 | Yes | Latest 8.x patch | | `PowerShellStandard.Library` | 5.1.1 | Yes | PrivateAssets=all | | `System.Management.Automation` | 7.4.0 | Yes | PrivateAssets=all | | `Microsoft.NET.Test.Sdk` | 17.9.0 | Yes | Test only | | `xunit` | 2.7.0 | Yes | Test only | | `Moq` | 4.20.70 | Yes | Test only | | `coverlet.collector` | 6.0.1 | Yes | Test only | All dependencies are pinned to exact versions. No floating version ranges detected. ### 5.5 Secret Scanning | Finding | Severity | Location | |---|---|---| | Hardcoded test password | **Medium** | `integration-tests.yml:42` -- `PVE_PASSWORD: "Testpass123!"` | The test password is for a throwaway nested PVE instance provisioned and destroyed during CI. It is masked in CI logs via `::add-mask::`. This is acceptable for CI test infrastructure but should be documented. No other hardcoded credentials, tokens, or sensitive IPs found in source code. --- ## Phase 6 -- PSGallery Publication Readiness ### 6.1 Module Manifest Assessment | Field | Status | Value | |---|---|---| | `RootModule` | OK | `PSProxmoxVE.dll` | | `ModuleVersion` | OK | `0.1.0` | | `GUID` | OK | `a3f7c2d1-84e5-4b9f-a061-3e2d8c5f1a7b` | | `Author` | OK | `goodolclint` | | `CompanyName` | OK | `Worklab` | | `Copyright` | OK | `(c) 2026 goodolclint. All rights reserved.` | | `Description` | OK | Meaningful, not placeholder | | `PowerShellVersion` | OK | `5.1` | | `CompatiblePSEditions` | OK | `Desktop`, `Core` | | `DotNetFrameworkVersion` | OK | `4.8` | | `RequiredAssemblies` | OK | Lists Core DLL and Newtonsoft.Json | | `FormatsToProcess` | OK | `PSProxmoxVE.format.ps1xml` | | `CmdletsToExport` | **Excellent** | Explicit list of all 66 cmdlets | | `FunctionsToExport` | OK | Empty (binary module) | | `AliasesToExport` | OK | Empty | | `VariablesToExport` | OK | Empty | | `Prerelease` | OK | `preview` | | `Tags` | OK | 8 relevant tags | | `LicenseUri` | OK | Points to GitHub LICENSE | | `ProjectUri` | OK | Points to GitHub repo | | `ReleaseNotes` | **Missing** | Not present in PSData | | `IconUri` | **Missing** | Not present in PSData | | `RequiredModules` | N/A | None required | | `HelpInfoURI` | **Missing** | No online help | ### 6.2 License - MIT License present at repo root. - OSI-approved (PSGallery requirement met). - `LicenseUri` in manifest points to it. ### 6.3 README Quality | Aspect | Status | |---|---| | Installation instructions | **Present** but says "not PSGallery" -- needs update for publication | | Quick-start examples | **Excellent** -- multiple scenarios covered | | Authentication guide | **Excellent** -- both ticket and API token documented | | Multi-cluster usage | **Excellent** -- `-Session` parameter documented | | Cmdlet reference table | **Excellent** -- all cmdlets listed with descriptions | | Known limitations | **Good** -- v1 limitations documented | | Contributing section | **Present** -- basic development setup | | Badges | **Missing** -- no build status, PSGallery version, or license badges | ### 6.4 Build & Publish Pipeline - **Build workflow**: Builds and tests on net48 (Windows) and net9.0 (Windows + Ubuntu). - **Unit test workflow**: Tests Pester scripts across PS 5.1/7.5 on Windows/Ubuntu/macOS. - **Integration test workflow**: Full tests against live PVE 8 and PVE 9. - **PSGallery publish workflow**: **MISSING** -- no workflow for `Publish-Module` or release creation. - **GitHub Releases**: Not configured (no release workflow, no tagged releases in git history). ### 6.5 Versioning - CHANGELOG follows Keep a Changelog format with Conventional Commits. - Only an `[Unreleased]` section exists -- no versioned releases yet. - Commit messages follow conventional commits (`feat:`, `fix:`, `test:`, `ci:`, etc.). --- ## Phase 7 -- Community & Repo Maintenance Standards ### Files Present | Item | Status | |---|---| | `README.md` | Present, comprehensive | | `LICENSE` | Present (MIT) | | `CHANGELOG.md` | Present | | `.editorconfig` | Present | | `.gitignore` | Present | ### Files Missing | Item | Recommendation | |---|---| | `CONTRIBUTING.md` | Create with dev setup, coding standards, test instructions, PR process | | `CODE_OF_CONDUCT.md` | Adopt Contributor Covenant | | `SECURITY.md` | Create with vulnerability disclosure policy | | `.gitattributes` | Create for line ending normalization (`* text=auto`) | | `.github/ISSUE_TEMPLATE/` | Create bug report and feature request templates | | `.github/pull_request_template.md` | Create with checklist | ### Commit Conventions - Conventional Commits used consistently. - Scopes include: `ci`, `test`, `fix`, `feat`, `refactor`, `chore`, `docs`. - Recent history shows disciplined commit hygiene. ### Branch Protection Recommended settings (cannot verify via API): - Default branch: `main` (confirmed) - Require PR reviews before merge - Require status checks to pass (build + unit tests + integration tests) - Require linear history or squash merging --- ## Phase 8 -- Prioritized Recommendations ### Critical (blocks PSGallery publication or is a security risk) | # | What | Where | Why | Fix | |---|---|---|---|---| | C1 | **No PSGallery publish workflow** | `.github/workflows/` | Cannot automate releases | Create `publish.yml` triggered on tag push that runs `Publish-Module` with PSGallery API key from secrets | | C2 | **README says "not PSGallery"** | `README.md:30` | Contradicts publication goal | Update installation section to `Install-Module PSProxmoxVE` | | C3 | **No ReleaseNotes in manifest** | `PSProxmoxVE.psd1:168-195` | PSGallery strongly recommends release notes | Add `ReleaseNotes` to `PSData` section | | ~~C4~~ | ~~Pipeline processing bug~~ | *Verified: all cmdlets use `ProcessRecord`* | N/A -- pipeline processing is correct | No action needed | ### High (significantly impacts quality or community adoption) | # | What | Where | Why | Fix | |---|---|---|---|---| | H1 | **No `HelpMessage` on parameters** | All cmdlet files | `Get-Help` shows no parameter descriptions | Add `HelpMessage = "..."` to all `[Parameter]` attributes | | H2 | **No `WriteVerbose` in cmdlets** | All cmdlets except Connection | Users cannot debug API calls | Add `WriteVerbose($"Calling {method} {resource}")` before API calls | | H3 | **Missing `CONTRIBUTING.md`** | Repo root | Barrier to community contributions | Create with dev setup, coding standards, PR process | | H4 | **Missing `SECURITY.md`** | Repo root | No vulnerability disclosure process | Create with responsible disclosure instructions | | H5 | **No warning on `-SkipCertificateCheck`** | `ConnectPveServerCmdlet.cs` | Users may not realize they're disabling TLS verification | Add `WriteWarning("Certificate validation is disabled...")` | | H6 | **`Reset-PveVm` uses string literal** | `ResetPveVmCmdlet.cs:16` | Inconsistent with all other cmdlets using verb class constants | Change `"Reset"` to `VerbsCommon.Reset` | | ~~H7~~ | ~~No `WriteProgress` in `Wait-PveTask`~~ | *Verified: already implemented* | N/A -- `WaitPveTaskCmdlet` already uses `WriteProgress` with percentage and timeout tracking | No action needed | | H8 | **Missing GitHub issue/PR templates** | `.github/` | No structured issue reporting | Create bug report, feature request, and PR templates | ### Medium (best practice gaps, test coverage holes) | # | What | Where | Why | Fix | |---|---|---|---|---| | M1 | **`Stop-PveVm`/`Reset-PveVm` lack `ConfirmImpact.High`** | `StopPveVmCmdlet.cs`, `ResetPveVmCmdlet.cs` | These can cause data loss | Add `ConfirmImpact = ConfirmImpact.High` | | M2 | **No `[ValidateRange]` on VmId** | All cmdlets with VmId parameter | PVE requires VMID 100-999999999 | Add `[ValidateRange(100, 999999999)]` to all VmId parameters | | M3 | **Container snapshots not supported** | Services/Cmdlets | Containers support snapshots in PVE but module only handles VM snapshots | Add `Get/New/Remove/Restore-PveContainerSnapshot` cmdlets | | M4 | **No integration tests for network modifications** | Integration tests | Network CRUD is tested only at Pester level | Add integration tests for network create/modify/delete | | M5 | **No `CODE_OF_CONDUCT.md`** | Repo root | Expected for open-source projects | Add Contributor Covenant | | M6 | **No `.gitattributes`** | Repo root | Line ending consistency across platforms | Add `* text=auto` and specific overrides | | M7 | **CS1591 suppressed in Core project** | `PSProxmoxVE.Core.csproj:10` | Public API lacks XML documentation | Remove NoWarn and add XML docs to public members | | M8 | **No badges in README** | `README.md` | Reduces discoverability and trust | Add build status, PSGallery version, license badges | | M9 | **CHANGELOG has only [Unreleased]** | `CHANGELOG.md` | No release history | Create first versioned release entry | | M10 | **`Invoke-PveVmGuestExec` lacks ShouldProcess** | `InvokePveVmGuestExecCmdlet.cs:14` | Executes commands inside a VM -- a mutating operation | Add `SupportsShouldProcess = true` | ### Low / Nice-to-have (polish, discoverability, stretch goals) | # | What | Where | Why | Fix | |---|---|---|---|---| | L1 | **No `IconUri` in manifest** | `PSProxmoxVE.psd1` | Improves PSGallery listing appearance | Create a module icon and host it, add URI | | L2 | **No online help (MAML/platyPS)** | Module | `Get-Help -Online` doesn't work | Generate MAML help using platyPS | | L3 | **No firewall cmdlets** | Module | High-value gap for security automation | Implement firewall rule CRUD for cluster/node/VM | | L4 | **No backup/vzdump cmdlets** | Module | High-value gap for DR automation | Implement vzdump and backup schedule management | | L5 | **No pool management** | Module | Useful for multi-tenant environments | Implement pool CRUD | | L6 | **`new Random()` for boundary generation** | `PveHttpClient.cs:393` | Not cryptographically secure (not a security risk for multipart boundaries, but could use `RandomNumberGenerator`) | Consider `RandomNumberGenerator` for .NET 6+ | | L7 | **No alias support** | Module manifest | Some users prefer short aliases | Consider adding aliases like `cpve` for `Connect-PveServer` | | L8 | **Tagged releases in GitHub** | Repository | No GitHub Releases | Set up release workflow with auto-generated release notes | | L9 | **Container migration cmdlet** | Module | VMs have `Move-PveVm` but containers have no equivalent | Add `Move-PveContainer` | | L10 | **SDN subnet management** | Module | SDN zones/vnets exist but subnets are missing | Add SDN subnet CRUD | --- ## Summary Statistics | Metric | Count | |---|---| | Total cmdlets | 66 | | Cmdlets with ShouldProcess | 43 (all destructive/mutating) | | Cmdlets with OutputType | 66 (100%) | | xUnit test files | 13 | | Pester test files | 26 | | Integration test contexts | 13 | | PVE API areas covered | 10 of ~20 major areas | | Critical issues | 4 | | High issues | 8 | | Medium issues | 10 | | Low issues | 10 | | NuGet dependencies (runtime) | 2 (Newtonsoft.Json, System.Text.Json) | | Security vulnerabilities found | 0 | | Hardcoded secrets | 1 (test password in CI, masked) |