diff --git a/.env.example b/.env.example index 86feb63..a6d2378 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,8 @@ DEFAULT_ADMIN_PASSWORD=change-me-now DEFAULT_ADMIN_NAME=POSH Admin POWERSHELL_BIN=pwsh ALLOW_SCRIPT_EXECUTION=true +# Minutes between dynamic Host Group rule syncs. 0 disables the worker. +HOST_GROUP_SYNC_INTERVAL_MINUTES=60 # Minutes between automatic catalog upstream-version checks. 0 disables the worker. CATALOG_CHECK_INTERVAL_MINUTES=0 # Minutes between auto-promote soak checks. 0 disables the worker. diff --git a/README.md b/README.md index 5368de2..8318c1e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The current UI is a Vue/Vite view layer over the Express API. All create/update/ - PSADT Best Practice Verifier for saved scripts, rendered profiles, and pasted script content with severity scoring, line-aware findings, remediation text, and rule metadata. - Intune Publishing Wizard for PSADT Win32 deployment plans, including package source, `.intunewin` tracking, command style, UI compatibility, requirements, detection rules, return-code policy, assignment rings, status, Graph app ID, and visibility. - Microsoft Graph Intune tracking for PSADT deployments, with Graph environment configuration managed under Config / Intune plus mobile app linking, status sync, failure review, device/user status rows, and sync run history. -- Host Library with searchable/sortable/paginated table, modal add/edit flow, and VMware vCenter VM import wizard with preview filters and credential attachment. +- Host Library with searchable/sortable/paginated table, modal add/edit flow, VMware vCenter VM import wizard, and manual/dynamic Host Groups for RunPlan and script-test targeting. - Credential Vault as its own menu item with encrypted-at-rest secrets and modal add/edit flow. - RunPlan Library with searchable/sortable/paginated table, modal composer, and execute action. - Aggregate job log viewer with search/filter/pagination. @@ -165,6 +165,7 @@ Important environment variables: | `VCENTER_API_MODE` | `auto`, `api`, or `rest`. Defaults to `auto`, which tries modern `/api` then legacy `/rest` on incompatible endpoints. | | `VCENTER_REQUEST_TIMEOUT_MS` | Per-request vCenter HTTP timeout. Defaults to `20000`. | | `VCENTER_ALLOW_UNTRUSTED_TLS` | Set `true` to allow self-signed/private CA vCenter certificates. | +| `HOST_GROUP_SYNC_INTERVAL_MINUTES` | Minutes between dynamic Host Group rule syncs. Defaults to `60`; set `0` to disable the worker. | | `CATALOG_CHECK_INTERVAL_MINUTES` | Minutes between catalog upstream-version checks. `0` disables. | | `AUTO_PROMOTE_INTERVAL_MINUTES` | Minutes between auto-promote soak checks. `0` disables. | | `TOOLS_DIR` | Directory for bundled tools. Defaults to `./tools`. | @@ -505,9 +506,20 @@ Payload: | --- | --- | --- | --- | | `GET` | `/api/hosts` | User | List host library. | | `POST` | `/api/hosts` | User | Create host. | -| `GET` | `/api/hosts/import/vcenter/status` | User | Return effective vCenter import configuration status without the password. | +| `GET` | `/api/hosts/import/vcenter/status` | User | Return legacy effective vCenter status plus visible saved vCenter systems. | +| `GET` | `/api/hosts/import/vcenter/connections` | User | List visible saved vCenter systems. | +| `POST` | `/api/hosts/import/vcenter/connections` | User | Create an encrypted vCenter system record. | +| `PUT` | `/api/hosts/import/vcenter/connections/:connectionId` | User | Update a visible vCenter system; omit password to keep the existing secret. | +| `DELETE` | `/api/hosts/import/vcenter/connections/:connectionId` | User | Delete a visible vCenter system. | +| `POST` | `/api/hosts/import/vcenter/connections/:connectionId/test` | User | Authenticate and list VM summaries to verify a vCenter system. | | `POST` | `/api/hosts/import/vcenter/preview` | User | Authenticate to vCenter, search VM inventory, and return import candidates. | | `POST` | `/api/hosts/import/vcenter` | User | Import selected vCenter VM candidates as hosts, attaching the chosen credential/defaults. | +| `GET` | `/api/hosts/groups` | User | List visible manual and dynamic Host Groups with member counts. | +| `POST` | `/api/hosts/groups` | User | Create a manual or dynamic Host Group. | +| `PUT` | `/api/hosts/groups/:groupId` | User | Update a visible Host Group. Manual groups accept explicit host members; dynamic groups accept rules. | +| `DELETE` | `/api/hosts/groups/:groupId` | User | Delete a visible Host Group and its membership rows. | +| `POST` | `/api/hosts/groups/:groupId/sync` | User | Recalculate one dynamic Host Group immediately. | +| `POST` | `/api/hosts/groups/sync` | User | Recalculate all dynamic Host Groups immediately. | | `PUT` | `/api/hosts/:id` | User | Update host. | | `DELETE` | `/api/hosts/:id` | User | Delete host. | @@ -525,15 +537,88 @@ Payload: "tags": ["production", "database"], "notes": "Primary SQL host", "visibility": "shared", - "groupId": null + "groupId": null, + "sourceType": "manual", + "sourceConnectionId": null, + "sourceRef": "" } ``` `transport` can be `winrm`, `ssh`, `local`, or `api`. `visibility` can be `personal`, `shared`, or `group`; hosts default to `shared`. RunPlans can only -target hosts visible to the operator who creates or edits them. +target hosts visible to the operator who creates or edits them. `sourceType`, +`sourceConnectionId`, and `sourceRef` are hidden provenance fields. Manual +hosts default to `sourceType: "manual"`. vCenter imports set +`sourceType: "vmware"`, `sourceConnectionId` to the selected vCenter system, +and `sourceRef` to the vCenter VM id. -VMware vCenter import uses the effective Config / Integrations values (`vcenter_*` settings or the `VCENTER_*` environment variables). `vcenter_api_mode` can be `auto`, `api`, or `rest`. `api` uses the current vSphere Automation API flow (`POST /api/session`, `GET /api/vcenter/vm`). `rest` uses the legacy/community vCenter REST flow (`POST /rest/com/vmware/cis/session`, `GET /rest/vcenter/vm`). `auto` tries the current `/api` profile first and falls back to `/rest` when the endpoint is not supported. +Host Groups let operators aggregate manual, imported, and VMware-sourced hosts +without duplicating RunPlans. Manual groups store an explicit list of visible +host IDs that an operator can add/remove in the Hosts screen. Dynamic groups +store rules and are recalculated by the API worker every +`host_group_sync_interval_minutes` minutes, by the +`HOST_GROUP_SYNC_INTERVAL_MINUTES` environment variable, or on demand with the +Sync Now actions. Dynamic group membership is rule-owned, so the UI does not +allow hand-picking members for those groups. + +Create a manual Host Group: + +```json +{ + "name": "Engineering Manual", + "description": "Pinned engineering test machines.", + "mode": "manual", + "hostIds": ["hst_101", "hst_205"], + "visibility": "shared", + "groupId": null +} +``` + +Create a dynamic Host Group: + +```json +{ + "name": "Engineering", + "description": "Any visible host whose name contains ENG.", + "mode": "dynamic", + "matchMode": "all", + "rules": [ + { "field": "name", "operator": "contains", "value": "ENG" } + ], + "visibility": "shared", + "groupId": null +} +``` + +Dynamic group rule fields are `name`, `fqdn`, `address`, `tags`, `transport`, +`sourceType`, and `osFamily`. Operators can combine rules with `matchMode: +"all"` or `matchMode: "any"`. Operators can use `contains`, `equals`, +`startsWith`, and `endsWith` operators. Rule matching is evaluated only against +hosts the group owner/creator can see through personal/shared/group visibility. + +Create/update a saved vCenter system: + +```json +{ + "name": "Production vCenter", + "baseUrl": "https://vcenter.contoso.local", + "username": "administrator@vsphere.local", + "password": "stored-encrypted-on-save", + "apiMode": "auto", + "requestTimeoutMs": 20000, + "allowUntrustedTls": false, + "enabled": true, + "visibility": "shared", + "groupId": null +} +``` + +Saved vCenter passwords are encrypted with the same credential-store key used +by the Credential Vault. Existing `vcenter_*` settings and `VCENTER_*` +environment variables remain supported as the `Configured default` import +connection for single-vCenter deployments or Docker-provided configuration. + +VMware vCenter import can use a saved vCenter system selected by `connectionId` or the legacy configured default from Config / Integrations (`vcenter_*` settings or `VCENTER_*` environment variables). `apiMode` / `vcenter_api_mode` can be `auto`, `api`, or `rest`. `api` uses the current vSphere Automation API flow (`POST /api/session`, `GET /api/vcenter/vm`). `rest` uses the legacy/community vCenter REST flow (`POST /rest/com/vmware/cis/session`, `GET /rest/vcenter/vm`). `auto` tries the current `/api` profile first and falls back to `/rest` when the endpoint is not supported. For exact VM-name searches (`field: "name"`, `operator: "equals"`), POSHManager uses the documented server-side name filters: `names=` for `/api/vcenter/vm` and `filter.names=` for `/rest/vcenter/vm`. Contains/starts-with/ends-with searches are filtered locally because vCenter VM list filters do not provide wildcard contains semantics. The combined `hostname` field searches both the VM inventory name and the VMware Tools guest `host_name`/FQDN after enrichment. VM display-name matches are checked first, then the rest of the inventory is still enriched for guest-only hostname matches. Networking/IP enrichment is only requested for candidates that match the filter. FQDN and IP discovery depends on VMware Tools data; when guest data is unavailable, the import falls back to the VM name as the host address and records the limitation in host notes. @@ -547,6 +632,7 @@ Preview payload: ```json { + "connectionId": "vc_...", "filter": { "field": "hostname", "operator": "contains", @@ -560,6 +646,7 @@ Import payload: ```json { + "connectionId": "vc_...", "filter": { "field": "hostname", "operator": "contains", @@ -582,6 +669,14 @@ previewed candidates that match the filter are considered. Existing visible hosts with the same name, address, or FQDN are skipped and returned in the `skipped` array so imports can be rerun safely. +When a script test or RunPlan targets a host with `sourceType: "vmware"`, the +runner checks the current vCenter power state for `sourceRef` through the stored +vCenter connection before spawning PowerShell. VMs that are not `POWERED_ON` +are logged and marked `skipped` for that host, so powered-off machines are not +treated like script failures. If the vCenter connection is missing or the +management API is temporarily unavailable, the runner logs the reason and +continues execution rather than blocking the whole job on a control-plane issue. + ### Script Library | Method | Route | Auth | Description | @@ -1008,7 +1103,9 @@ The catalog returned by `/api/psadt/catalog` also includes `intune` coverage for ### RunPlans And Jobs -RunPlans pair one script with one or more host IDs. Execution creates a job with per-host log rows. +RunPlans pair one script with one or more direct host IDs, Host Group IDs, or +both. Execution expands Host Groups into their current members and creates a job +with per-host log rows. | Method | Route | Auth | Description | | --- | --- | --- | --- | @@ -1031,10 +1128,17 @@ RunPlan payload: "visibility": "shared", "groupId": null, "parallel": true, - "hostIds": ["hst_..."] + "hostIds": ["hst_..."], + "hostGroupIds": ["hg_..."] } ``` +`hostIds` target individual hosts. `hostGroupIds` target visible Host Groups. +Manual groups run against their saved members. Dynamic groups run against the +membership from the latest scheduler/manual sync, so use the Hosts screen Sync +Now action before execution when a just-imported host should be picked up +immediately. + Execute: ```bash diff --git a/client/src/App.vue b/client/src/App.vue index 0ffa284..596602c 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -213,6 +213,7 @@ :open="scriptTestModalOpen" :script="scriptTestTarget" :hosts="hosts" + :host-groups="hostGroups" :credentials="credentials" :job="scriptTestJob" :running="scriptTestRunning" @@ -331,10 +332,21 @@ + + +
@@ -406,7 +418,7 @@ - +