64 KiB
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 andPOSHM_ASSET_MANIFESTruntime 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,
.intunewintracking, 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, VMware vCenter VM import wizard, standalone ESXi connection records, 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.
- 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:sqlitewith 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
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.vueowns dashboard page composition and emits layout actions.client/src/components/dashboard/DashboardWidget.vueowns the reusable widget card frame and widget-body extension point.client/src/components/dashboard/WidgetLibraryModal.vueowns restore/add UI for hidden widgets.- New widgets should be added to the dashboard catalog in
App.vue, then rendered inDashboardWidget.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
cp .env.example .env
npm install
npm run dev
Open http://localhost:3000.
Default development login:
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
/apito 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.
npm run build
npm run prod
Docker
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_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. |
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:
Authorization: Bearer <jwt>
Content-Type: application/json
Login:
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:
{
"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:
$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)
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,
.intunewinparsing, and validation logic are unit-tested. See PLAN-INTUNE.md.
Graph connection payload:
{
"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:
$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:
{
"displayName": "Automation Lead",
"email": "operator@example.com",
"jobTitle": "Platform Engineer",
"phone": "+1 555 0100",
"timezone": "America/Chicago",
"avatarUrl": ""
}
Theme payload:
{ "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:
{
"email": "operator@example.com",
"displayName": "Automation Operator",
"password": "temporary-password",
"role": "user",
"groupIds": ["grp_..."]
}
Create group:
{
"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:
{
"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 legacy effective vCenter status plus visible saved VMware connection records. |
GET |
/api/hosts/import/vcenter/connections |
User | List visible saved VMware connections. |
POST |
/api/hosts/import/vcenter/connections |
User | Create an encrypted VMware connection record. Use targetType: "vcenter" for inventory or targetType: "host" for standalone ESXi Web Services testing. |
PUT |
/api/hosts/import/vcenter/connections/:connectionId |
User | Update a visible VMware connection; omit password to keep the existing secret. |
DELETE |
/api/hosts/import/vcenter/connections/:connectionId |
User | Delete a visible VMware connection. |
POST |
/api/hosts/import/vcenter/connections/:connectionId/test |
User | Verify a VMware connection. vCenter records authenticate and list VM summaries; standalone host records call the vSphere Web Services SOAP endpoint. |
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. |
Payload:
{
"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,
"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. 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. osFamily is normalized to windows,
linux, or other; older values such as network or api are treated as
other. Manual host entry should set this explicitly when POSHManager cannot
infer it from inventory. vCenter imports infer Windows/Linux from VMware Tools
guest identity when available; unknown guests are imported as other.
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:
{
"name": "Engineering Manual",
"description": "Pinned engineering test machines.",
"mode": "manual",
"hostIds": ["hst_101", "hst_205"],
"visibility": "shared",
"groupId": null
}
Create a dynamic Host Group:
{
"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.
The Host Groups Target Collections table includes search, sort, pagination, and
a read-only summary action. The summary view displays the rule/mode and assigned
machines, then exports CSV or Excel-readable .xls files with hostname,
IP address, and vCenter/VMware source system columns.
Create/update a saved vCenter inventory system:
{
"name": "Production vCenter",
"targetType": "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
}
Create/update a saved standalone ESXi host connection:
{
"name": "Standalone ESXi 01",
"targetType": "host",
"baseUrl": "https://esxi01.contoso.local",
"username": "root",
"password": "stored-encrypted-on-save",
"apiMode": "web-services",
"requestTimeoutMs": 20000,
"allowUntrustedTls": true,
"enabled": true,
"visibility": "shared",
"groupId": null
}
Saved VMware passwords are encrypted with the same credential-store key used
by the Credential Vault. targetType: "vcenter" records use the vSphere
Automation API or legacy REST API for inventory and VM power-state checks.
targetType: "host" records are standalone host connections that use the
vSphere Web Services SOAP endpoint at /sdk; they validate direct ESXi
connectivity but cannot perform vCenter VM inventory imports. 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=<vm> for /api/vcenter/vm and filter.names=<vm> 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.
For field: "name" filters, the API scans the full VM summary list returned by vCenter before guest enrichment, so large inventories are not accidentally missed by the preview limit. The preview response includes diagnostics with totalFromVCenter, summaryMatched, scanned, resultLimit, and sampleNames; the wizard displays those values when troubleshooting a no-result preview.
Each preview/import response also includes a redacted trace array. The wizard opens a vCenter API trace modal immediately when Preview starts, then replaces the running entry with the captured trace when the API responds. Operators can inspect the actual request URL, redacted headers, HTTP status, duration, response summary, and truncated response body from each vCenter call. Basic auth and vmware-api-session-id values are never shown in the trace.
If vCenter DNS/TCP/TLS/authentication hangs, the backend aborts each vCenter HTTP request after vcenter_request_timeout_ms and returns a traceable timeout entry. The browser also adds watchdog entries while it is still waiting for the POSHManager API response, so operators can distinguish a pending backend call from an empty trace.
Preview payload:
{
"connectionId": "vc_...",
"filter": {
"field": "hostname",
"operator": "contains",
"value": "ENG-ENT"
},
"limit": 100
}
Import payload:
{
"connectionId": "vc_...",
"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.
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.
Standalone ESXi host connection records do not provide vCenter VM inventory
state, so VMware-sourced imported guests continue to use their saved vCenter
source connection for pre-run power checks.
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. |
POST |
/api/scripts/:id/compatibility |
User | Analyze a script against selected host or Host Group targets before execution. |
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:
{
"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:
{
"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:
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:
{
"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:
$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:
{
"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:
{
"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:
$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.ps1scaffold 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:
{
"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:
{
"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
142public*-ADT*functions fromsrc/PSAppDeployToolkit/Public, - all
40ADMX policies fromsrc/PSAppDeployToolkit/opt/ADMX/PSAppDeployToolkit.admx, - upstream launch helper command patterns from
examples/Launching, - the upstream v4
Invoke-AppDeployToolkit.ps1frontend 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:
{
"platform": "intune",
"content": "Invoke-AppDeployToolkit.ps1 content here"
}
Or a saved script/profile:
{ "platform": "intune", "scriptId": "scr_..." }
{ "platform": "intune", "profileId": "psadt_..." }
Verifier response:
{
"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:
{
"targetVersion": "4.2.0",
"scriptId": "scr_..."
}
Raw content can be migrated without a saved script:
{
"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:
{
"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:
{
"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:
- Prepare source: record the PSADT source folder,
Files,SupportFiles,Assets,Config,Strings, and generated.intunewin. - Program commands: choose PSADT v4
Invoke-AppDeployToolkit.execommands or legacy v3Deploy-Application.execompatibility commands. - Requirements: capture architecture, minimum OS, disk space, and 32-bit execution mode.
- Detection: model MSI product code, file/version, registry, or custom PowerShell detection.
- Return codes: map success, retry/defer, soft reboot, PSADT failure, and custom ranges.
- Assignments: track pilot, broad, uninstall, and exclusion rings.
- 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 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 |
|---|---|---|---|
GET |
/api/runplans |
User | List visible RunPlans. |
POST |
/api/runplans |
User | Create RunPlan. |
GET |
/api/runplans/:id |
User | Read RunPlan. |
GET |
/api/runplans/:id/compatibility |
User | Analyze the RunPlan script against its current direct and Host Group targets. |
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:
{
"name": "Patch validation sweep",
"description": "Validate core services after patching.",
"scriptId": "scr_...",
"visibility": "shared",
"groupId": null,
"parallel": true,
"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:
curl -X POST http://localhost:3000/api/runplans/rp_123/execute \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{}'
Linux compatibility preflight:
curl -X POST http://localhost:3000/api/scripts/scr_123/compatibility \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"hostIds":["hst_linux_01"],"hostGroupIds":["hg_mixed_targets"]}'
curl http://localhost:3000/api/runplans/rp_123/compatibility \
-H "Authorization: Bearer $TOKEN"
The compatibility response includes the selected target OS mix and findings
when Linux hosts are involved. The analyzer flags common cross-platform issues
such as Windows registry providers, C:\ or UNC paths, Windows-only
executables such as msiexec.exe/reg.exe, WMI/Win32_* patterns,
Windows-specific environment variables, and alias-heavy script style. RunPlan
execution and Script Test / Run also write the same warning summary into
per-host job logs for Linux targets.
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:
{
"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.
localhosts run the script directly in the API container.winrmhosts useInvoke-Command -ComputerName.sshhosts useInvoke-Command -HostName.- Host credential resolution happens inside backend execution flows.
- POSHManager runtime variables are injected as PowerShell variables and
POSHM_*environment variables. POSHM_HOST_OS_FAMILYis injected withwindows,linux, orotherso scripts can branch on the target OS type.- 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 asPOSHM_ASSET_MANIFESTJSON. 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=falseto 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.
PowerShell on Linux requires a supported distro package/runtime and scripts must
avoid assuming Windows-only subsystems. Prefer full cmdlet names over aliases,
branch OS-specific logic with $IsWindows/$IsLinux, and test modules/cmdlets
on the target platform before broad execution.
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_executioncan be flipped at runtime from the Config screen; the database value takes precedence over the boot-timeALLOW_SCRIPT_EXECUTIONenvironment variable.- In production (
NODE_ENV=production) the API refuses to start whileJWT_SECRET,CREDENTIAL_STORE_KEY, orDEFAULT_ADMIN_PASSWORDare left at their development defaults. - The default admin credential is for first-run development only.
- Store
JWT_SECRETandCREDENTIAL_STORE_KEYas container secrets or environment variables in production.
Developer Workflow
Useful commands:
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:
node --input-type=module path/to/smoke-script.mjs
Recommended implementation boundaries:
- Add API behavior in
server/routes,server/controllers,server/forms, andserver/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, andcomponents/settings. - Avoid adding more large page logic to
App.vue; move new full views into dedicated files as the app grows.