# POSHManager

POSHManager is an API-first PowerShell operations console for storing scripts, managing target hosts and encrypted credentials, building RunPlans, executing jobs, and reviewing per-host and system logs.

The current UI is a Vue/Vite view layer over the Express API. All create/update/delete/execute actions go through backend routes so another GUI, CLI, or automation client can reuse the same platform.

## Features

- Secure local login with JWT sessions.
- Editable operator profile, avatar URL, local password change, and per-user theme preference.
- Microsoft Entra ID interactive single sign-on (OAuth authorization-code flow), configuration fields, and Entra password-flow handoff support. Entra login auto-provisions the user and is offered only when fully configured; local login always remains available.
- Script workspace modeled after the SysProv template editor:
  - left Script Library rail with folders and right-click context menu actions,
  - VS Code-style Monaco PowerShell editor with syntax highlighting, minimap, folding, hover, snippets, and autocomplete,
  - right Details rail for metadata, visibility, stats, and actions,
  - close/reopen controls for Script Library and Details rails,
  - Variable Studio for POSHManager runtime variables, PSADT reference variables, and custom variables,
  - inline suggestions for PowerShell cmdlets, PSADT functions/snippets, POSHManager variables, custom variables, and Asset Library tokens.
- Script folders and script version history through the API.
- Asset Library for deployment package content:
  - folder tree organization for installers, icons, configs, archives, detection scripts, and support files,
  - authenticated browser/API upload, preview, download, delete, and metadata updates,
  - link records that associate assets with scripts, RunPlans, PSADT profiles, or Intune deployment plans,
  - `{{asset:<id>}}` script tokens and `POSHM_ASSET_MANIFEST` runtime metadata for non-PSADT PowerShell usage.
- PSAppDeployToolkit-focused authoring support covering the PSADT variable reference catalog.
- Custom script variables with personal/shared/group visibility, encrypted values, editor insertion, and RunPlan injection.
- PSADT Workbench with support matrix, deployment structure reference, Invoke-AppDeployToolkit parameters, persistent deployment profiles, generated scaffolds, migration wizard, and Intune-ready command lines.
- Upstream PSADT repo coverage for module metadata, all public functions, launch helper commands, ADMX policies, and the v4 frontend template shape.
- 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.
- 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.
- System log tab for Winston request/application logs.
- Users & Groups administration with modal create flows.
- Application Config screen with collapsible sections and boolean switch controls.
- Floating searchable online Help Library from the top navigation with operations docs, API examples, PSADT/Intune deployment guidance, snippets, and common PowerShell cmdlet references.
- Docker-friendly runtime configuration through environment variables.

## Stack

- Node.js 24, Express, CORS, Helmet, Multer
- Built-in `node:sqlite` with prepared statements
- Vue 3, Vite, Vuetify, Tailwind CSS, Monaco Editor, lucide icons
- Winston request/system logging
- AES-256-GCM credential encryption
- Playwright for browser smoke checks

## Project Layout

```text
server/
  routes/          Express route registration only
  controllers/     HTTP validation, status codes, and request/response mapping
  models/          SQLite persistence and query functions
  forms/           Zod schemas and payload normalization
  middleware/      auth and request logging middleware
  services/        encryption, PowerShell runner, Winston logger

client/src/
  views/           top-level route/view components such as Login, Dashboard, Error
  plugins/         Vue plugin setup such as Vuetify theme/defaults
  components/
    dashboard/     dashboard widget frame/content and widget library modal
    assets/        asset tree and asset preview/details components
    scripts/       script-authoring helpers such as Variable Studio
    settings/      reusable settings/config section components
    ui/            reusable modal/table primitives
    AppSidebar.vue
    AppTopbar.vue
    ParticleBackdrop.vue
  api.js           fetch wrapper with auth token injection
  App.vue          page composition and orchestration
  style.css        Tailwind entrypoint plus shared Material/fluid visual system
```

The backend already follows route/controller/model/form/service separation. The frontend now keeps top-level pages in `client/src/views` and uses reusable table, modal, dashboard-widget, and config-section components for repeated UI patterns. Future large screens should become view components first, with smaller domain-specific pieces under folders such as `components/scripts`, `components/hosts`, and `components/runplans`.

Tailwind CSS is installed through the Vite plugin and imported from `client/src/style.css`. Vuetify setup is centralized in `client/src/plugins/vuetify.js` so Material-style defaults, theme roles, density, icons, and component behavior are controlled in one place instead of scattered through views.

Dashboard widgets are intentionally split:

- `client/src/views/DashboardView.vue` owns dashboard page composition and emits layout actions.
- `client/src/components/dashboard/DashboardWidget.vue` owns the reusable widget card frame and widget-body extension point.
- `client/src/components/dashboard/WidgetLibraryModal.vue` owns restore/add UI for hidden widgets.
- New widgets should be added to the dashboard catalog in `App.vue`, then rendered in `DashboardWidget.vue`.

Script authoring helpers are split under `client/src/components/scripts`. `VariableEditorModal.vue` owns the PSADT/POSHManager variable catalog browser and custom variable editor so future PSADT snippets, Intune deployment helpers, and variable packs can be added without bloating `App.vue`.

## Quick Start

```bash
cp .env.example .env
npm install
npm run dev
```

Open `http://localhost:3000`.

Default development login:

```text
Email: admin@posh.local
Password: change-me-now
```

Change default credentials and secrets before deploying.

## Runtime Modes

`npm run dev` starts both services:

- Vite UI: `http://localhost:3000`
- Express API: `http://127.0.0.1:5174`
- Vite proxies `/api` to the API, so the browser uses one frontend origin.

`npm run prod` serves the built Vue UI through Vite preview on port `3000` and starts the API on port `5174`.

```bash
npm run build
npm run prod
```

## Docker

```bash
docker compose up --build
```

The container exposes Vite on port `3000`; `/api` is proxied internally to Express. SQLite data and logs are stored in the `poshmanager-data` volume. The Docker image installs PowerShell 7 for execution workflows.

## Configuration

Configuration is visible in the UI under Config and is grouped into collapsible sections. Database-backed settings can be edited in the UI; environment-backed values are shown so Docker/reverse-proxy configuration remains visible.

Important environment variables:

| Variable | Description |
| --- | --- |
| `NODE_ENV` | `development` or `production`. |
| `PORT` | Internal Express API port. Defaults to `5174`. |
| `SERVER_FQDN` | Public app URL used for generated links and runtime display. |
| `CLIENT_ORIGIN` | Primary allowed browser origin. |
| `CLIENT_ORIGINS` | Comma-separated trusted origins for CORS. |
| `API_PROXY_TARGET` | Vite proxy target for `/api`. Defaults to `http://127.0.0.1:5174`. |
| `DATABASE_PATH` | SQLite file path. |
| `LOG_DIR` | Winston log directory. |
| `FILE_LOCKER_DIR` | Filesystem root for uploaded assets/packages/images/scripts/configs. Defaults to `./FileLocker`. Mount this in Docker for persistence. |
| `ASSET_DIR` | Deprecated compatibility alias for older deployments; new installs should use `FILE_LOCKER_DIR`. |
| `MAX_ASSET_BYTES` | Maximum size of one uploaded asset in bytes. Defaults to `52428800` (50 MiB). |
| `JSON_LIMIT` | Express JSON request body limit for non-file API calls. Asset uploads use multipart `multer`. |
| `JWT_SECRET` | JWT signing key. Must be changed in production. |
| `CREDENTIAL_STORE_KEY` | Encryption key material for credential secrets. Must be changed in production. |
| `DEFAULT_ADMIN_EMAIL` | First-run admin email. |
| `DEFAULT_ADMIN_PASSWORD` | First-run admin password. |
| `DEFAULT_ADMIN_NAME` | First-run admin display name. |
| `POWERSHELL_BIN` | PowerShell executable. Defaults to `pwsh`. |
| `ALLOW_SCRIPT_EXECUTION` | Set `false` to disable actual RunPlan execution. |
| `VCENTER_ENABLED` | Enables VMware vCenter VM discovery and Host import workflows. |
| `VCENTER_BASE_URL` | vCenter REST API root, for example `https://vcenter.contoso.local`. |
| `VCENTER_USERNAME` | vCenter account used by the backend to create API sessions. |
| `VCENTER_PASSWORD` | vCenter password. Prefer environment pinning for production deployments. |
| `VCENTER_ALLOW_UNTRUSTED_TLS` | Set `true` to allow self-signed/private CA vCenter certificates. |
| `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`. |
| `INTUNEWIN_UTIL_PATH` | Path to `IntuneWinAppUtil.exe` (defaults to `<TOOLS_DIR>/IntuneWinAppUtil.exe`). |
| `INTUNEWIN_BUILD_COMMAND` | Cross-platform packager command template (`{source}`,`{setup}`,`{output}`). |
| `ENTRA_ENABLED` | Enables Entra-related auth behavior when fully configured. |
| `ENTRA_TENANT_ID` | Microsoft tenant id. |
| `ENTRA_CLIENT_ID` | Microsoft app registration client id. |
| `ENTRA_CLIENT_SECRET` | Microsoft app registration secret. |
| `ENTRA_CALLBACK_URL` | Entra sign-in callback URL placeholder. |
| `ENTRA_PASSWORD_CHANGE_URL` | URL opened for Entra-backed password changes. |

## API Basics

All protected routes require:

```http
Authorization: Bearer <jwt>
Content-Type: application/json
```

Login:

```bash
curl -s http://localhost:3000/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@posh.local","password":"change-me-now"}'
```

The response includes:

```json
{
  "token": "jwt-token",
  "user": {
    "id": "usr_...",
    "email": "admin@posh.local",
    "displayName": "POSH Admin",
    "role": "admin"
  }
}
```

## API Routes

### Health And Bootstrap

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/health` | Public | API health check. |
| `GET` | `/api/bootstrap` | User | Summary counts and settings snapshot for the UI. |

### Help Library

The online Help Library is available from the top navigation and is backed by API routes so future clients can reuse the same searchable documentation. The floating panel can be moved around the screen and searches operations, API examples, PSADT/Intune guidance, snippets, and PowerShell cmdlet references.

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/help` | User | Search help article summaries. Supports `q` and `category` query parameters. |
| `GET` | `/api/help/:id` | User | Return one full help article with sections, paragraphs, code examples, and tags. |

PowerShell search example:

```powershell
$base = "http://localhost:3000"
$token = (Invoke-RestMethod -Method Post -Uri "$base/api/auth/login" -ContentType "application/json" -Body (@{
  email = "admin@posh.local"
  password = "change-me-now"
} | ConvertTo-Json)).token
$headers = @{ Authorization = "Bearer $token" }

Invoke-RestMethod -Method Get -Uri "$base/api/help?q=psadt&category=PSADT" -Headers $headers
Invoke-RestMethod -Method Get -Uri "$base/api/help/psadt-intune-deploy" -Headers $headers
```

The help library includes local summaries for common `Microsoft.PowerShell.Core` cmdlets and links operators back to the Microsoft reference at `https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/?view=powershell-5.1`.

### Microsoft Graph And Intune Tracking

Graph environments store Microsoft Entra app registration details used to call Microsoft Graph for Intune app metadata and deployment reporting. In the UI, these are managed under Config / Intune. Client secrets are encrypted at rest and are never returned by the API. At minimum, read-only tracking should use app-only permissions such as `DeviceManagementApps.Read.All`; creating or changing app assignments requires broader permissions such as `DeviceManagementApps.ReadWrite.All`.

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/graph/connections` | User | List visible Graph environments without secrets. |
| `POST` | `/api/graph/connections` | User | Create a Graph environment with encrypted client secret. |
| `PUT` | `/api/graph/connections/:id` | User | Update Graph environment metadata or rotate the client secret. |
| `DELETE` | `/api/graph/connections/:id` | User | Delete a Graph environment. |
| `POST` | `/api/graph/connections/:id/test` | User | Acquire a token and verify Intune app read access. |
| `GET` | `/api/graph/connections/:id/mobile-apps` | User | Search Intune mobile apps for linking to POSHManager deployment plans. Supports `q`. |
| `POST` | `/api/psadt/intune/deployments/:id/graph/link` | User | Link an Intune Publishing Wizard plan to a Graph environment and mobileApp id. |
| `POST` | `/api/psadt/intune/deployments/:id/graph/sync` | User | Sync device/user install status and failure rows from Graph Intune reports. |
| `GET` | `/api/psadt/intune/deployments/:id/graph/status` | User | Return stored status rows, failure rows, summary counts, and sync run history. |
| `POST` | `/api/psadt/intune/deployments/:id/graph/publish` | User | Create/update the `win32LobApp` metadata in the tenant from the plan. Requires a write-enabled Graph connection. |
| `POST` | `/api/psadt/intune/deployments/:id/graph/upload-content` | User | Upload + commit `.intunewin` content (from an Asset Library `assetId`) to the published app. |
| `POST` | `/api/psadt/intune/deployments/:id/graph/assign` | User | Replace the app's assignments (rings, filters, exclusions, auto-update). |
| `POST` | `/api/psadt/intune/deployments/:id/graph/relationships` | User | Replace the app's supersedence/dependency relationships. |
| `POST` | `/api/psadt/intune/deployments/:id/graph/drift` | User | Compare the plan against the live tenant app/assignments/relationships. |
| `POST` | `/api/psadt/intune/deployments/:id/graph/reconcile` | User | Re-apply the plan (metadata+assignments+relationships) to clear drift. |
| `POST` | `/api/psadt/intune/deployments/:id/new-version` | User | Clone the plan into the next version, linked to the same catalog application. |
| `POST` | `/api/psadt/intune/deployments/:id/promote` | User | Advance a rollout ring's assignment intent (e.g. available → required). |
| `GET` | `/api/psadt/intune/builder/status` | User | Whether this host can build `.intunewin` (Windows + tool, or external packager). |
| `POST` | `/api/psadt/intune/deployments/:id/build` | User | Build a `.intunewin` from a source folder and ingest it into the Asset Library. |
| `GET` | `/api/packaging/installer-types` | User | List known installer technologies and their silent-switch catalog. |
| `POST` | `/api/packaging/analyze` | User | Detect installer technology from a file name and recommend install/uninstall commands. |
| `POST` | `/api/packaging/detection` | User | Generate an Intune detection rule (MSI product code, file+version, or registry). |

### Installer intelligence (packaging)

`/api/packaging/analyze` turns an installer file name into a detected technology
(MSI, MSP, Inno Setup, NSIS, InstallShield, WiX Burn, MSIX/AppX, Squirrel, or
generic EXE) with recommended silent install/uninstall commands, a PSADT
`Start-ADT*` form, and a confidence level; ambiguous `.exe` files return all
candidate technologies so the operator can pick. `/api/packaging/detection`
generates a detection rule in the deployment's `detectionType`/`detectionRule`
shape — an MSI product-code rule, or an Intune-style custom PowerShell script for
file-version or registry detection. The deployment modal's **Analyze installer**
control fills the install/uninstall commands directly.
| `GET` | `/api/psadt/intune/deployments/:id/graph/audit` | User | Return the audit trail of tenant-changing Graph actions for this deployment. |
| `GET` | `/api/intune/applications` | User | List catalog applications with computed update state. |
| `POST` | `/api/intune/applications` | User | Create a catalog application (groups versioned deployments). |
| `POST` | `/api/intune/applications/import` | User | Adopt an existing tenant Win32 app into the catalog (Graph read). |
| `GET` | `/api/intune/applications/:id` | User | Read one catalog application. |
| `PUT` | `/api/intune/applications/:id` | User | Update a catalog application. |
| `DELETE` | `/api/intune/applications/:id` | User | Delete a catalog application. |
| `POST` | `/api/intune/applications/:id/check-update` | User | Look up the upstream version and record the latest known version. |
| `POST` | `/api/intune/applications/check-all` | Admin | Run the upstream check for every auto-check application now. |
| `GET` | `/api/intune/change-requests` | User | List tenant-change approval requests (visible deployments; admins see all). |
| `POST` | `/api/intune/change-requests/:id/approve` | Admin | Approve a pending change request. |
| `POST` | `/api/intune/change-requests/:id/reject` | Admin | Reject a pending change request. |
| `GET` | `/api/intune/reporting/overview` | User | Rollout reporting: deployments by status, install-state counts, top errors, write-action outcomes. |
| `GET` | `/api/intune/deployments/:id/audit/export` | User | CSV export of the tenant write-back audit trail for a deployment. |

### Governance (Phase 6)

A Graph connection can require approval (`requireApproval`, admin-only). When set,
any tenant-changing action (publish / upload-content / assign / relationships)
does not run immediately — it creates a **change request** and returns `202`. An
admin approves it via `/change-requests/:id/approve`, and the next attempt
executes and marks the request executed. Governance flags (`allowWrite`,
`requireApproval`) can only be granted by admins and are preserved on non-admin
edits. The audit trail is CSV-exportable, and `/reporting/overview` aggregates
rollout health across the deployments an operator can see. The scheduled catalog
auto-checker runs when `CATALOG_CHECK_INTERVAL_MINUTES` is greater than zero.

A connection can also name **authorized publishers** (`publisherIds` — who may
trigger writes; empty means any user with a write-enabled connection) and
**authorized approvers** (`approverIds` — who may approve change requests
alongside admins). Both lists are admin-managed. **Drift detection**
(`/graph/drift`) compares the stored plan against the live tenant app,
assignments, and relationships, returning field-level differences plus
missing/extra assignments and relationships so operators can spot out-of-band
tenant changes. **Reconcile** (`/graph/reconcile`) re-applies the full plan to
clear drift. The **promotion pipeline** clones a plan into the next version
(`/new-version`, linked to the same catalog application) and advances rollout
rings (`/promote`, e.g. a Broad ring from *available* to *required*); the
operator builds/uploads and assigns the new version.

### Packaging (`.intunewin` build) and auto-promote

POSHManager can build the `.intunewin` itself: place
`IntuneWinAppUtil.exe` in the `tools/` directory (see [tools/README.md](tools/README.md))
and use the **Build package** action — it runs the Content Prep Tool over the
deployment's source folder and ingests the result into the Asset Library, ready
for content upload. The build host must be Windows; on other platforms set
`INTUNEWIN_BUILD_COMMAND` to a cross-platform packager (`{source}`, `{setup}`,
`{output}` placeholders). The UI disables Build when no packager is available.

A deployment can also **auto-promote**: set "auto-promote after (hours)", a ring,
and an intent. Publishing starts the soak clock; the scheduled worker
(`AUTO_PROMOTE_INTERVAL_MINUTES > 0`) advances the ring's intent once the soak
window elapses, and — when the linked connection is write-enabled and not
approval-gated — pushes the new assignments to the tenant automatically.

### Application catalog (Phase 5)

A catalog **application** groups versioned deployment plans
(`intune_deployments.application_id`) and tracks the currently published
`mobileApp`. Each read computes `updateState.updateAvailable` by comparing
`currentVersion` to `latestKnownVersion`. A version source can be `manual`, a
`url` returning JSON with a `version` field, or a `winget` community index ref;
`check-update` fetches and records the latest version. `import` adopts an
existing tenant Win32 app (display name / publisher / version / app id) into the
catalog as a migration aid. Version lookups and import hit live sources; the
comparison, update-state, and Graph→catalog mapping are unit-tested.

### Tenant write-back (Phase 1)

Publishing to Intune is opt-in and gated. A Graph connection must have **Allow
tenant write-back** enabled (admin-only), which requires the broader
`DeviceManagementApps.ReadWrite.All` application permission. Read-only tracking
connections can never change a tenant.

`POST .../graph/publish` builds a Microsoft Graph `win32LobApp` object from the
deployment plan (display name/publisher from the linked PSADT profile, install/
uninstall commands, install experience, return codes, and detection rules) and
creates or updates it in the tenant. The created `mobileApp` id is stored back on
the deployment and the plan is marked `published`. Every attempt — success or
failure — is recorded in the Graph audit log with the actor, the exact request
payload, and the Graph response.

After publishing the app definition, `.../graph/upload-content` makes it
installable: it reads a `.intunewin` package from the Asset Library, parses the
encrypted payload and `Detection.xml` encryption info locally, then runs the
Graph content-version → Azure block upload → commit flow and sets the committed
content version. `.../graph/assign` replaces the app's assignments (group /
all-devices / all-users targets, include/exclude filters, exclusions, and
available-app auto-update), and `.../graph/relationships` replaces supersedence
(update/replace) and dependency (detect/autoInstall) relationships with 11-node
and cycle validation.

> The content upload and assignment/relationship writes drive a live tenant and
> are validated end to end only against a real Intune tenant; the payload mapping,
> `.intunewin` parsing, and validation logic are unit-tested. See [PLAN-INTUNE.md](PLAN-INTUNE.md).

Graph connection payload:

```json
{
  "name": "Production Intune",
  "tenantId": "contoso.onmicrosoft.com",
  "clientId": "00000000-0000-0000-0000-000000000000",
  "clientSecret": "client-secret-value",
  "cloud": "global",
  "graphBaseUrl": "https://graph.microsoft.com",
  "authorityHost": "https://login.microsoftonline.com",
  "defaultApiVersion": "v1.0",
  "enabled": true,
  "visibility": "shared"
}
```

Deployment link and sync examples:

```powershell
$body = @{ connectionId = "graph_123"; graphAppId = "intune-mobile-app-id" }
Invoke-RestMethod -Method Post -Uri "$base/api/psadt/intune/deployments/intune_123/graph/link" -Headers $headers -ContentType "application/json" -Body ($body | ConvertTo-Json)

$sync = @{ connectionId = "graph_123"; graphAppId = "intune-mobile-app-id"; mode = "reports" }
Invoke-RestMethod -Method Post -Uri "$base/api/psadt/intune/deployments/intune_123/graph/sync" -Headers $headers -ContentType "application/json" -Body ($sync | ConvertTo-Json)
Invoke-RestMethod -Method Get -Uri "$base/api/psadt/intune/deployments/intune_123/graph/status" -Headers $headers
```

Status rows preserve the Intune app id, scope (`device` or `user`), device/user identifiers, install state, install detail, error code, inferred error description, last sync time, and raw Graph/report payload for troubleshooting. POSHManager prefers Intune reporting endpoints for tracking because older `deviceStatuses` and `userStatuses` relationships have changed over time; legacy status mode is kept as a fallback for tenants where it still works.

### Auth And Profile

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `POST` | `/api/auth/login` | Public | Local login. Rate limited per client IP. |
| `GET` | `/api/auth/entra/login` | Public | Redirect to Microsoft sign-in (404 when Entra login is not fully configured). |
| `GET` | `/api/auth/entra/callback` | Public | Entra OAuth redirect target; provisions the user and hands the SPA a session token. |
| `GET` | `/api/auth/me` | User | Current session user. |
| `PUT` | `/api/auth/profile` | User | Update display name, contact fields, timezone, avatar URL. |
| `PUT` | `/api/auth/theme` | User | Update theme preference. |
| `POST` | `/api/auth/change-password` | User | Change local password or return Entra redirect metadata. |

Profile payload:

```json
{
  "displayName": "Automation Lead",
  "email": "operator@example.com",
  "jobTitle": "Platform Engineer",
  "phone": "+1 555 0100",
  "timezone": "America/Chicago",
  "avatarUrl": ""
}
```

Theme payload:

```json
{ "theme": "dashtreme" }
```

### Users And Groups

Admin-only routes create users and groups. Groups are used for group-visible scripts, credentials, and RunPlans.

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/users` | Admin | List users with group memberships. |
| `POST` | `/api/users` | Admin | Create a local user. |
| `GET` | `/api/groups` | User | List groups. |
| `POST` | `/api/groups` | Admin | Create a group. |

Create user:

```json
{
  "email": "operator@example.com",
  "displayName": "Automation Operator",
  "password": "temporary-password",
  "role": "user",
  "groupIds": ["grp_..."]
}
```

Create group:

```json
{
  "name": "Operations",
  "description": "Shared scripts and RunPlans",
  "userIds": []
}
```

### Credential Vault

Credential reads never return the secret. Secrets are encrypted with AES-256-GCM before storage.

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/credentials` | User | List visible credentials. |
| `POST` | `/api/credentials` | User | Create encrypted credential. |
| `PUT` | `/api/credentials/:id` | User | Update metadata or replace secret. |
| `DELETE` | `/api/credentials/:id` | User | Delete visible credential. |

Payload:

```json
{
  "name": "Domain automation account",
  "kind": "username_password",
  "username": "svc-poshmanager",
  "secret": "super-secret-value",
  "visibility": "group",
  "groupId": "grp_..."
}
```

`kind` can be `username_password` or `api_key`. `visibility` can be `personal`, `shared`, or `group`.

### Hosts

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `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. |
| `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. |
| `PUT` | `/api/hosts/:id` | User | Update host. |
| `DELETE` | `/api/hosts/:id` | User | Delete host. |

Payload:

```json
{
  "name": "SQL-PROD-01",
  "fqdn": "sql-prod-01.contoso.local",
  "address": "sql-prod-01.contoso.local",
  "osFamily": "windows",
  "transport": "winrm",
  "port": null,
  "credentialId": "cred_...",
  "tags": ["production", "database"],
  "notes": "Primary SQL host",
  "visibility": "shared",
  "groupId": null
}
```

`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.

VMware vCenter import uses the effective Config / Integrations values (`vcenter_*` settings or the `VCENTER_*` environment variables). The backend creates a vCenter REST session with `POST /api/session`, lists VMs from `/api/vcenter/vm`, and then best-effort enriches each candidate from guest identity/networking endpoints. 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.

Preview payload:

```json
{
  "filter": {
    "field": "hostname",
    "operator": "contains",
    "value": "ENG-ENT"
  },
  "limit": 100
}
```

Import payload:

```json
{
  "filter": {
    "field": "hostname",
    "operator": "contains",
    "value": "ENG-ENT"
  },
  "limit": 100,
  "vmIds": ["vm-101", "vm-205"],
  "credentialId": "cred_...",
  "transport": "winrm",
  "port": null,
  "tags": ["engineering", "vcenter-import"],
  "visibility": "group",
  "groupId": "grp_..."
}
```

`field` can be `hostname`, `name`, `fqdn`, or `ip`. `operator` can be
`contains`, `equals`, `startsWith`, or `endsWith`. If `vmIds` is empty, all
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.

### Script Library

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/folders` | User | List visible folders. |
| `POST` | `/api/folders` | User | Create folder. |
| `PUT` | `/api/folders/:id` | User | Update folder. |
| `DELETE` | `/api/folders/:id` | User | Delete folder. |
| `GET` | `/api/scripts` | User | List visible scripts. |
| `POST` | `/api/scripts` | User | Create script and initial version. |
| `GET` | `/api/scripts/:id` | User | Read script. |
| `PUT` | `/api/scripts/:id` | User | Update script and create a version record. |
| `DELETE` | `/api/scripts/:id` | User | Delete script. |
| `GET` | `/api/scripts/:id/versions` | User | List script versions. |

Create/update script:

```json
{
  "folderId": "fld_...",
  "name": "Sample Health Check.ps1",
  "description": "Simple smoke-test script for local execution.",
  "content": "Write-Output \"Hello\"",
  "visibility": "shared",
  "groupId": null
}
```

Create folder:

```json
{
  "parentId": null,
  "name": "Examples",
  "visibility": "personal",
  "groupId": null
}
```

### Asset Library

The Asset Library stores package-supporting files on disk under `FileLocker` and tracks metadata in SQLite. Use it for PSADT/Intune source content such as `Files/setup.exe`, `SupportFiles/config.json`, icons, detection scripts, archives, and reusable content referenced by normal PowerShell scripts.

Assets can be personal, shared, or group-visible. Folder organization is independent from script folders and is exposed through a tree view in the UI. Uploads use multipart `form-data` with a `file` field handled by `multer`; metadata is submitted alongside the file.

New files are staged in `FileLocker/_incoming`, then promoted to:

```text
FileLocker/
  assets/
    packages/<assetId>/<filename>   # installers, .intunewin, archives
    images/<assetId>/<filename>     # icons, banners, screenshots
    scripts/<assetId>/<filename>    # ps1, psm1, cmd, bat
    configs/<assetId>/<filename>    # json, xml, yaml, admx/adml, reg
    documents/<assetId>/<filename>  # txt, md, pdf, docs
    generic/<assetId>/<filename>    # everything else
```

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/assets/folders` | User | List visible asset folders. |
| `POST` | `/api/assets/folders` | User | Create an asset folder. |
| `PUT` | `/api/assets/folders/:id` | User | Rename, move, describe, or rescope an asset folder. |
| `DELETE` | `/api/assets/folders/:id` | User | Delete a visible asset folder. Child folders are removed and assets become unfiled. |
| `GET` | `/api/assets` | User | List visible asset metadata. |
| `POST` | `/api/assets` | User | Upload an asset with multipart `form-data`; file field name is `file`. |
| `GET` | `/api/assets/:id` | User | Read one asset metadata record. |
| `PUT` | `/api/assets/:id` | User | Update folder, name, description, package path, visibility, or group. |
| `DELETE` | `/api/assets/:id` | User | Delete asset metadata and its stored file. |
| `GET` | `/api/assets/:id/view` | User | Stream an asset inline for preview. |
| `GET` | `/api/assets/:id/download` | User | Download the stored asset file. |
| `GET` | `/api/assets/:id/links` | User | List link records for an asset. |
| `POST` | `/api/assets/:id/links` | User | Link an asset to a script, RunPlan, PSADT profile, or Intune deployment. |
| `DELETE` | `/api/assets/links/:linkId` | User | Delete an asset link record. |

Create an asset folder:

```json
{
  "parentId": null,
  "name": "Contoso VPN",
  "description": "Installers, icons, and support files for the VPN deployment.",
  "visibility": "shared",
  "groupId": null
}
```

Upload asset form fields:

| Field | Required | Description |
| --- | --- | --- |
| `file` | Yes | Uploaded binary file handled by `multer`. |
| `folderId` | No | Asset folder id. Omit for unfiled assets. |
| `name` | Yes | Display name. |
| `description` | No | Operational notes. |
| `packagePath` | No | Package-relative path such as `Files/setup.exe` or `SupportFiles/config.json`. |
| `visibility` | No | `personal`, `shared`, or `group`. |
| `groupId` | When group | Group id for group-visible assets. |

PowerShell upload example:

```powershell
$file = "/packages/ContosoVpnSetup.exe"
$form = @{
  file = Get-Item $file
  folderId = "afld_..."
  name = "Contoso VPN Installer"
  description = "Primary installer used by the PSADT package."
  packagePath = "Files/ContosoVpnSetup.exe"
  visibility = "shared"
}
Invoke-RestMethod -Method Post -Uri "$base/api/assets" -Headers $headers -Form $form
```

Link an asset to a target:

```json
{
  "targetType": "psadt_profile",
  "targetId": "psadt_...",
  "linkRole": "installer",
  "packagePath": "Files/ContosoVpnSetup.exe"
}
```

`targetType` can be `script`, `runplan`, `psadt_profile`, or `intune_deployment`. `linkRole` can be `reference`, `package-file`, `installer`, `support-file`, `icon`, or `detection-script`.

Each asset returns a `referenceToken` like `{{asset:ast_123}}`. During RunPlan execution, POSHManager replaces that token with the local stored file path and exposes all visible assets in `POSHM_ASSET_MANIFEST` as JSON. For PSADT/Intune package building, use `packagePath` to place the file into the correct package-relative location such as `Files/setup.exe`, `SupportFiles/config.json`, or `Assets/app.ico`.

### Variables And PSADT Authoring

Variable APIs expose the built-in POSHManager runtime variables, the PSAppDeployToolkit reference variables, and user-defined custom variables. The Vue editor consumes the same API used by external clients.

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/variables/catalog` | User | Return POSHManager built-ins, PSADT variables, and visible custom variables. |
| `GET` | `/api/variables/custom` | User | List visible custom variables. |
| `POST` | `/api/variables/custom` | User | Create a custom variable. |
| `PUT` | `/api/variables/custom/:id` | User | Update a custom variable; omit `value` to keep an existing encrypted value. |
| `DELETE` | `/api/variables/custom/:id` | User | Delete a visible custom variable. |

Custom variable payload:

```json
{
  "name": "CompanyName",
  "description": "Deployment branding value used by PSADT install scripts.",
  "category": "Deployment",
  "value": "Contoso",
  "valueType": "string",
  "sensitive": false,
  "visibility": "group",
  "groupId": "grp_..."
}
```

Custom variable names must be valid PowerShell identifiers without the `$` prefix. They can be inserted as `$CompanyName` or token syntax such as `{{CompanyName}}`. Values are encrypted at rest with the same AES-GCM service used by the Credential Vault. Non-sensitive custom values are returned for editing; sensitive values are available to execution but hidden from API/UI reads.

POSHManager runtime variables available during execution include:

```text
$POSHM_JOB_ID
$POSHM_RUNPLAN_ID
$POSHM_SCRIPT_ID
$POSHM_SCRIPT_NAME
$POSHM_HOST_ID
$POSHM_HOST_NAME
$POSHM_HOST_ADDRESS
$POSHM_HOST_TRANSPORT
$POSHM_TRIGGERED_BY
```

PSADT variables are cataloged from the PSAppDeployToolkit variable reference for authoring help and insertion. PSADT itself creates those values when the deployment session is opened by `Invoke-AppDeployToolkit.ps1` or exported into session state.

### PSADT Workbench

The PSADT Workbench models the PSAppDeployToolkit deployment lifecycle as first-class API data. It currently supports the first implementation part of the PSADT integration:

- support matrix for the referenced PSADT documentation areas,
- upstream repo metadata for PSAppDeployToolkit module version/GUID,
- deployment structure reference table,
- Invoke-AppDeployToolkit parameter reference table,
- full public-function catalog from the upstream module,
- ADMX policy surface catalog,
- launch/test command catalog,
- UI and installer snippets moved into the searchable Help Library,
- persistent deployment profiles,
- generated `Invoke-AppDeployToolkit.ps1` scaffold content,
- generated package/Intune command lines,
- best-practice verification for scripts and profiles,
- script migration planning from legacy PSADT scripts to the latest supported PSADT template/function names,
- stored Intune deployment plans for Win32 app command, detection, return-code, assignment-ring, and rollout-status tracking.

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/psadt/catalog` | User | Return PSADT source links, module metadata, support matrix, structure rows, invoke parameters, public functions, ADMX policies, launch commands, and snippets. |
| `GET` | `/api/psadt/validation-rules` | User | Return the PSADT verifier rule catalog. |
| `POST` | `/api/psadt/validate` | User | Validate raw script content, a saved script, or a rendered PSADT profile. |
| `POST` | `/api/psadt/migration/plan` | User | Build a PSADT migration plan for raw content or a saved script. |
| `POST` | `/api/psadt/migration/apply` | User | Apply automatic migration replacements and optionally create a migrated script copy. |
| `GET` | `/api/psadt/intune/deployments` | User | List visible PSADT-aware Intune deployment plans. |
| `POST` | `/api/psadt/intune/deployments` | User | Create an Intune deployment plan with commands, detection, return codes, and assignments. |
| `GET` | `/api/psadt/intune/deployments/:id` | User | Read a visible Intune deployment plan. |
| `PUT` | `/api/psadt/intune/deployments/:id` | User | Update an Intune deployment plan. |
| `DELETE` | `/api/psadt/intune/deployments/:id` | User | Delete an Intune deployment plan. |
| `GET` | `/api/psadt/profiles` | User | List visible PSADT deployment profiles. |
| `POST` | `/api/psadt/profiles` | User | Create a PSADT deployment profile. |
| `GET` | `/api/psadt/profiles/:id` | User | Read a visible PSADT profile. |
| `PUT` | `/api/psadt/profiles/:id` | User | Update a visible PSADT profile. |
| `DELETE` | `/api/psadt/profiles/:id` | User | Delete a visible PSADT profile. |
| `GET` | `/api/psadt/profiles/:id/render` | User | Render a profile into script scaffold and command-line metadata. |

Profile payload:

```json
{
  "name": "Microsoft Edge Enterprise",
  "appVendor": "Microsoft",
  "appName": "Edge",
  "appVersion": "126.0.0",
  "appArch": "x64",
  "appLang": "EN",
  "appRevision": "01",
  "templateVersion": "v4",
  "deploymentType": "Install",
  "deployMode": "Silent",
  "requireAdmin": true,
  "zeroConfig": false,
  "suppressReboot": true,
  "closeProcesses": [
    { "name": "msedge", "description": "Microsoft Edge" }
  ],
  "installTasks": [
    {
      "type": "exe",
      "phase": "Install",
      "filePath": "setup.exe",
      "arguments": "/S",
      "secureArguments": false
    }
  ],
  "uiPlan": { "welcome": true },
  "configPlan": {},
  "admxPlan": {},
  "visibility": "shared",
  "groupId": null
}
```

Render response:

```json
{
  "scriptName": "Microsoft-Edge-Enterprise.ps1",
  "deployCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Install -DeployMode Silent -SuppressRebootPassThru",
  "intuneInstallCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Install -DeployMode Silent -SuppressRebootPassThru",
  "intuneUninstallCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Uninstall -DeployMode Silent -SuppressRebootPassThru",
  "script": "generated PowerShell content"
}
```

Zero-config MSI profiles set `zeroConfig` to `true`, intentionally leave `appName` empty, and render a checklist-oriented scaffold for the one-MSI Files-folder deployment pattern. The Workbench does not ship or redistribute PSADT template/module files; it generates the authoring scaffold and operational metadata that belong in a deployment package.

The current upstream audit was performed against `psappdeploytoolkit/psappdeploytoolkit` commit `9d244f4` from 2026-06-24. POSHManager now models:

- module version `4.2.0`,
- module GUID `8c3c366b-8606-4576-9f2d-4051144f7ca2`,
- all `142` public `*-ADT*` functions from `src/PSAppDeployToolkit/Public`,
- all `40` ADMX policies from `src/PSAppDeployToolkit/opt/ADMX/PSAppDeployToolkit.admx`,
- upstream launch helper command patterns from `examples/Launching`,
- the upstream v4 `Invoke-AppDeployToolkit.ps1` frontend template shape.

Generated PSADT scripts now follow the upstream v4 frontend structure: phase scriptblocks via `New-Variable`, `$adtSession` metadata, local-package module import by GUID/version when available, `Remove-ADTHashtableNullOrEmptyValues`, `Open-ADTSession`, extension module imports, phase invocation, and `Close-ADTSession`. `DeployMode` defaults to `Auto`; POSHManager only emits explicit `-DeployMode` when a profile selects `Interactive`, `NonInteractive`, or `Silent`.

Verifier payloads can target content directly:

```json
{
  "platform": "intune",
  "content": "Invoke-AppDeployToolkit.ps1 content here"
}
```

Or a saved script/profile:

```json
{ "platform": "intune", "scriptId": "scr_..." }
```

```json
{ "platform": "intune", "profileId": "psadt_..." }
```

Verifier response:

```json
{
  "ok": false,
  "score": 58,
  "counts": {
    "total": 4,
    "critical": 1,
    "error": 1,
    "warning": 2,
    "info": 0
  },
  "findings": [
    {
      "ruleId": "INTUNE-SERVICEUI",
      "severity": "critical",
      "category": "Intune/security",
      "line": 12,
      "title": "ServiceUI usage found",
      "remediation": "Remove ServiceUI workarounds and rely on PSADT v4.1 native user-session UI handling."
    }
  ]
}
```

The first verifier rule pack covers PSADT v4/v4.1 requirements and known gotchas from the release notes, requirements, v4-to-v4.1 upgrade guidance, FAQ, installer guidance, and exit-code reference. Current checks include unsupported `#requires` versions, legacy v3 function names, `Deploy-Application` naming, legacy `$appName` style variables, `ServiceUI.exe`, Fluent UI prompt `-Icon`, raw `msiexec`, missing `-WaitForChildProcesses`, reserved PSADT exit-code ranges, global module imports, Intune defer retry concerns, and user-context `RequireAdmin` issues.

Migration plan payload:

```json
{
  "targetVersion": "4.2.0",
  "scriptId": "scr_..."
}
```

Raw content can be migrated without a saved script:

```json
{
  "targetVersion": "4.2.0",
  "content": "Deploy-Application.ps1\nExecute-Process -Path setup.exe\n$appName = 'Legacy App'",
  "createScript": true,
  "nameSuffix": "PSADT 4.2 migrated"
}
```

Migration response:

```json
{
  "targetVersion": "4.2.0",
  "summary": {
    "steps": 5,
    "automatic": 3,
    "manual": 2,
    "replacements": 3
  },
  "steps": [
    {
      "id": "MIG-FN-EXECUTE-PROCESS",
      "type": "replace",
      "automatic": true,
      "status": "ready",
      "title": "Execute-Process to Start-ADTProcess",
      "lines": [2],
      "replacement": "Start-ADTProcess"
    }
  ],
  "migratedContent": "updated PowerShell content",
  "migratedScript": null
}
```

Automatic migration currently covers common PSADT legacy names such as `Execute-Process`, `Execute-MSI`, `Execute-MSP`, `Show-InstallationWelcome`, `Show-InstallationPrompt`, `Write-Log`, `Deploy-Application.ps1/.exe`, and legacy `$appName` style metadata variables. Manual review items flag ServiceUI launch wrappers, older function-based deployment phase shape, raw `msiexec`, missing `Open-ADTSession`, and Intune deferral flows that need `-DeferRunInterval`.

Intune deployment plan payload:

```json
{
  "name": "Edge Enterprise - Pilot",
  "profileId": "psadt_...",
  "scriptId": "scr_...",
  "sourceFolder": "C:\\Packages\\Edge\\PSADT",
  "intunewinFile": "EdgeEnterprise.intunewin",
  "commandStyle": "v4",
  "installBehavior": "system",
  "restartBehavior": "return-code",
  "uiMode": "native-v4",
  "requirements": {
    "architecture": "x64",
    "minOs": "Windows 10 22H2",
    "diskSpaceMb": 512,
    "runAs32Bit": false
  },
  "status": "ready",
  "installCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Install",
  "uninstallCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Uninstall",
  "detectionType": "custom-script",
  "detectionRule": "PowerShell detection script or rule data",
  "returnCodes": [
    { "code": 0, "type": "success", "meaning": "Install completed successfully." },
    { "code": 1602, "type": "retry", "meaning": "User deferred or cancelled; let Intune retry." },
    { "code": 1703, "type": "softReboot", "meaning": "Legacy PSADT soft reboot mapping used by some Intune examples." },
    { "code": 3010, "type": "softReboot", "meaning": "Reboot required." }
  ],
  "assignments": [
    { "ring": "Pilot", "intent": "available", "groupName": "IT Pilot", "notes": "Validation ring." },
    { "ring": "Broad", "intent": "required", "groupName": "All Windows Devices", "notes": "Production rollout." },
    { "ring": "Exclusion", "intent": "exclude", "groupName": "App Exclusions", "notes": "Break-glass exclusions." }
  ],
  "graphAppId": "optional Intune mobileApp id",
  "visibility": "shared"
}
```

The PSADT screen presents these records through an Intune Publishing Wizard carousel:

1. Prepare source: record the PSADT source folder, `Files`, `SupportFiles`, `Assets`, `Config`, `Strings`, and generated `.intunewin`.
2. Program commands: choose PSADT v4 `Invoke-AppDeployToolkit.exe` commands or legacy v3 `Deploy-Application.exe` compatibility commands.
3. Requirements: capture architecture, minimum OS, disk space, and 32-bit execution mode.
4. Detection: model MSI product code, file/version, registry, or custom PowerShell detection.
5. Return codes: map success, retry/defer, soft reboot, PSADT failure, and custom ranges.
6. Assignments: track pilot, broad, uninstall, and exclusion rings.
7. Review: score compatibility for package readiness, command style, UI context, detection, return codes, and assignments.

When a deployment plan links a PSADT profile and commands are omitted, the API renders the profile and fills `intuneInstallCommand` and `intuneUninstallCommand`. `commandStyle: "legacy-v3"` generates the older `Deploy-Application.exe -DeploymentType "Install" -DeployMode "Interactive"` style used by many historical Intune examples; `commandStyle: "v4"` uses the current `Invoke-AppDeployToolkit.exe` naming from PSADT v4. `uiMode: "native-v4"` is preferred for PSADT v4.1+ because user prompts work from Intune SYSTEM installs without ServiceUI, while `uiMode: "serviceui-legacy"` is retained only for intentional older/v3 compatibility packages.

The catalog returned by `/api/psadt/catalog` also includes `intune` coverage for the publishing carousel, Win32 app packaging, requirements, UI compatibility modes, return code mapping, detection rule types, assignment rings, management actions, and PSADT/Intune gotchas such as avoiding `ServiceUI.exe` on v4.1+, using `-DeferRunInterval`, and keeping custom PSADT app codes out of reserved built-in ranges.

### RunPlans And Jobs

RunPlans pair one script with one or more host IDs. Execution creates a job with per-host log rows.

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/runplans` | User | List visible RunPlans. |
| `POST` | `/api/runplans` | User | Create RunPlan. |
| `GET` | `/api/runplans/:id` | User | Read RunPlan. |
| `PUT` | `/api/runplans/:id` | User | Update RunPlan. |
| `DELETE` | `/api/runplans/:id` | User | Delete RunPlan. |
| `POST` | `/api/runplans/:id/execute` | User | Queue/execute a RunPlan. |
| `GET` | `/api/jobs` | User | List jobs. |
| `GET` | `/api/jobs/:id` | User | Read job with logs. |

RunPlan payload:

```json
{
  "name": "Patch validation sweep",
  "description": "Validate core services after patching.",
  "scriptId": "scr_...",
  "visibility": "shared",
  "groupId": null,
  "parallel": true,
  "hostIds": ["hst_..."]
}
```

Execute:

```bash
curl -X POST http://localhost:3000/api/runplans/rp_123/execute \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{}'
```

### Settings And Logs

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| `GET` | `/api/settings` | User | List app settings with source metadata. |
| `PUT` | `/api/settings` | Admin | Update database-backed settings. |
| `GET` | `/api/logs/system?limit=300` | Admin | Read Winston system/request logs. |

Settings update:

```json
{
  "server_fqdn": "https://poshmanager.example.com",
  "trusted_origins": "https://poshmanager.example.com",
  "allow_script_execution": "true",
  "entra_enabled": "false"
}
```

## Execution Notes

The runner writes a temporary PowerShell wrapper and launches `pwsh` with environment variables instead of shell interpolation.

- `local` hosts run the script directly in the API container.
- `winrm` hosts use `Invoke-Command -ComputerName`.
- `ssh` hosts use `Invoke-Command -HostName`.
- Host credential resolution happens inside backend execution flows.
- POSHManager runtime variables are injected as PowerShell variables and `POSHM_*` environment variables.
- Custom variables are decrypted server-side, token-rendered for `{{VariableName}}`, and injected into the script block as PowerShell variables before execution.
- Asset references are token-rendered for `{{asset:<assetId>}}` and exposed as `POSHM_ASSET_MANIFEST` JSON. Local paths point to the API container asset store; remote scripts must copy or otherwise stage those files before using them on target systems.
- PSADT variables are not emulated by POSHManager; they are available when the script runs inside a valid PSAppDeployToolkit session.
- Set `ALLOW_SCRIPT_EXECUTION=false` to disable execution while keeping planning and UI workflows active.

Remote PowerShell prerequisites, firewall rules, remoting policy, SSH remoting, and target credentials must be configured outside POSHManager.

## Security Notes

- SQLite access uses prepared statements through `node:sqlite`.
- Credential secrets are encrypted with AES-256-GCM and are not returned by API reads.
- Request logs redact authorization and cookie headers.
- Protected routes use JWT auth.
- Admin-only APIs are guarded by `requireAdmin`.
- Jobs and their logs are scoped to the operator who triggered them, users who can see the linked RunPlan, and admins; they are not globally readable.
- The login endpoint is rate limited per client IP to slow credential guessing.
- Hosts carry personal/shared/group visibility; RunPlans cannot target hosts the operator cannot see.
- User-supplied Microsoft Graph endpoint URLs are validated against an allow-list of Microsoft Graph/login hosts (SSRF protection), enforced on write and again before every outbound call.
- `allow_script_execution` can be flipped at runtime from the Config screen; the database value takes precedence over the boot-time `ALLOW_SCRIPT_EXECUTION` environment variable.
- In production (`NODE_ENV=production`) the API refuses to start while `JWT_SECRET`, `CREDENTIAL_STORE_KEY`, or `DEFAULT_ADMIN_PASSWORD` are left at their development defaults.
- The default admin credential is for first-run development only.
- Store `JWT_SECRET` and `CREDENTIAL_STORE_KEY` as container secrets or environment variables in production.

## Developer Workflow

Useful commands:

```bash
npm run dev       # API + UI with Vite proxy on port 3000
npm run build     # production build
npm run prod      # API + Vite preview on port 3000
npm run check     # Node syntax check for server files
npm test          # node:test suite (visibility, settings, Entra, SSRF allow-list)
```

Browser smoke testing uses Playwright:

```bash
node --input-type=module path/to/smoke-script.mjs
```

Recommended implementation boundaries:

- Add API behavior in `server/routes`, `server/controllers`, `server/forms`, and `server/models`.
- Keep secrets and execution logic in `server/services`.
- Keep reusable Vue primitives in `client/src/components/ui`.
- Keep top-level Vue screens in `client/src/views` (`LoginView`, `DashboardView`, `ErrorView`, and future full pages).
- Keep dashboard widgets and widget-library UI in `client/src/components/dashboard`.
- Keep script authoring helpers in `client/src/components/scripts`.
- Keep domain-specific Vue components in folders such as `components/scripts`, `components/hosts`, `components/runplans`, and `components/settings`.
- Avoid adding more large page logic to `App.vue`; move new full views into dedicated files as the app grows.
